commit 5883d164d2ebb80e943fdc820f3e7da300e581d5 Author: hanif salafi Date: Sat Nov 15 22:46:24 2025 +0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff92df --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +debug.log +/.ideadebug.log diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e5ded44 --- /dev/null +++ b/.gitlab-ci.yml @@ -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/jaecoo-be:dev 103.82.242.92:8900/medols/jaecoo-be:dev + - docker push 103.82.242.92:8900/medols/jaecoo-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-campaignpool-be/build?token=autodeploycampaignpool \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..06753a7 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/app/database/entity/activity_log_types.entity.go b/app/database/entity/activity_log_types.entity.go new file mode 100644 index 0000000..d7d3ae0 --- /dev/null +++ b/app/database/entity/activity_log_types.entity.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"` +} diff --git a/app/database/entity/activity_logs.entity.go b/app/database/entity/activity_logs.entity.go new file mode 100644 index 0000000..1e7b7a8 --- /dev/null +++ b/app/database/entity/activity_logs.entity.go @@ -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()"` +} diff --git a/app/database/entity/advertisement.entity.go b/app/database/entity/advertisement.entity.go new file mode 100644 index 0000000..4dcc541 --- /dev/null +++ b/app/database/entity/advertisement.entity.go @@ -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()"` +} diff --git a/app/database/entity/ai_chat_logs.entity.go b/app/database/entity/ai_chat_logs.entity.go new file mode 100644 index 0000000..28bc854 --- /dev/null +++ b/app/database/entity/ai_chat_logs.entity.go @@ -0,0 +1,19 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type AIChatLogs struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + SessionID uint `json:"session_id" gorm:"type:int4;not null;index"` + UserID uint `json:"user_id" gorm:"type:int4;not null;index"` + StartDate time.Time `json:"start_date" gorm:"not null"` + EndDate *time.Time `json:"end_date"` + TotalDuration int64 `json:"total_duration" gorm:"type:bigint;default:0"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` +} + diff --git a/app/database/entity/ai_chat_messages.entity.go b/app/database/entity/ai_chat_messages.entity.go new file mode 100644 index 0000000..d35062e --- /dev/null +++ b/app/database/entity/ai_chat_messages.entity.go @@ -0,0 +1,14 @@ +package entity + +import ( + "time" +) + +type AIChatMessages struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + SessionID string `json:"session_id" gorm:"type:varchar;not null;index"` + MessageType string `json:"message_type" gorm:"type:varchar;not null"` + Content string `json:"content" gorm:"type:text;not null"` + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` +} diff --git a/app/database/entity/ai_chat_sessions.entity.go b/app/database/entity/ai_chat_sessions.entity.go new file mode 100644 index 0000000..00d9474 --- /dev/null +++ b/app/database/entity/ai_chat_sessions.entity.go @@ -0,0 +1,19 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type AIChatSessions struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + SessionID string `json:"session_id" gorm:"type:varchar;not null;unique;index"` + UserID uint `json:"user_id" gorm:"type:int4;not null;index"` + AgentID string `json:"agent_id" gorm:"type:varchar;not null"` + Title string `json:"title" gorm:"type:varchar;not null"` + MessageCount int `json:"message_count" gorm:"type:int4;default:0"` + IsActive bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` +} diff --git a/app/database/entity/article_approvals.entity.go b/app/database/entity/article_approvals.entity.go new file mode 100644 index 0000000..e321bcc --- /dev/null +++ b/app/database/entity/article_approvals.entity.go @@ -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()"` +} diff --git a/app/database/entity/article_categories.entity.go b/app/database/entity/article_categories.entity.go new file mode 100644 index 0000000..6eb890d --- /dev/null +++ b/app/database/entity/article_categories.entity.go @@ -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()"` +} diff --git a/app/database/entity/article_category_details/article_category_details.entity.go b/app/database/entity/article_category_details/article_category_details.entity.go new file mode 100644 index 0000000..6fe8040 --- /dev/null +++ b/app/database/entity/article_category_details/article_category_details.entity.go @@ -0,0 +1,16 @@ +package article_category_details + +import ( + entity "campaign-pool-be/app/database/entity" + "time" +) + +type ArticleCategoryDetails struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ArticleId uint `json:"article_id" gorm:"type:int4"` + CategoryId int `json:"category_id" gorm:"type:int4"` + Category *entity.ArticleCategories `json:"category" gorm:"foreignKey:CategoryId;references:ID"` + IsActive bool `json:"is_active" gorm:"type:bool"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/article_comments.entity.go b/app/database/entity/article_comments.entity.go new file mode 100644 index 0000000..68eb427 --- /dev/null +++ b/app/database/entity/article_comments.entity.go @@ -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 diff --git a/app/database/entity/article_files.entity.go b/app/database/entity/article_files.entity.go new file mode 100644 index 0000000..7317069 --- /dev/null +++ b/app/database/entity/article_files.entity.go @@ -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()"` +} diff --git a/app/database/entity/articles.entity.go b/app/database/entity/articles.entity.go new file mode 100644 index 0000000..c316667 --- /dev/null +++ b/app/database/entity/articles.entity.go @@ -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()"` +} diff --git a/app/database/entity/audit_trails.entity.go b/app/database/entity/audit_trails.entity.go new file mode 100644 index 0000000..b93f3ee --- /dev/null +++ b/app/database/entity/audit_trails.entity.go @@ -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 +} diff --git a/app/database/entity/campaign_approvals.entity.go b/app/database/entity/campaign_approvals.entity.go new file mode 100644 index 0000000..7201855 --- /dev/null +++ b/app/database/entity/campaign_approvals.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" + + "gorm.io/gorm" +) + +type CampaignApprovals struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + CampaignID uint `json:"campaign_id" gorm:"type:int4"` + Campaign Campaigns `json:"campaign" gorm:"foreignKey:CampaignID;references:ID"` + ApproverID uint `json:"approver_id" gorm:"type:int4"` + Approver users.Users `json:"approver" gorm:"foreignKey:ApproverID;references:ID"` + Status string `json:"status" gorm:"type:varchar(20);default:'pending'"` // pending, approved, rejected + Notes *string `json:"notes" gorm:"type:text"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} diff --git a/app/database/entity/campaign_chats.entity.go b/app/database/entity/campaign_chats.entity.go new file mode 100644 index 0000000..8a63c32 --- /dev/null +++ b/app/database/entity/campaign_chats.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" + "gorm.io/gorm" +) + +type CampaignChats struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + CampaignID uint `json:"campaign_id" gorm:"type:int4"` + Campaign Campaigns `json:"campaign" gorm:"foreignKey:CampaignID;references:ID"` + SenderID uint `json:"sender_id" gorm:"type:int4"` + Sender users.Users `json:"sender" gorm:"foreignKey:SenderID;references:ID"` + Message string `json:"message" gorm:"type:text"` + AttachmentURL *string `json:"attachment_url" gorm:"type:text"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} + diff --git a/app/database/entity/campaign_destination_relations.entity.go b/app/database/entity/campaign_destination_relations.entity.go new file mode 100644 index 0000000..0041058 --- /dev/null +++ b/app/database/entity/campaign_destination_relations.entity.go @@ -0,0 +1,22 @@ +package entity + +import ( + "time" + + "gorm.io/gorm" +) + +type CampaignDestinationRelations struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + CampaignID uint `json:"campaign_id" gorm:"type:int4"` + Campaign Campaigns `json:"campaign" gorm:"foreignKey:CampaignID;references:ID"` + DestinationID uint `json:"destination_id" gorm:"type:int4"` + Destination CampaignDestinations `json:"destination" gorm:"foreignKey:DestinationID;references:ID"` + ScheduledAt *time.Time `json:"scheduled_at" gorm:"type:timestamp"` + PublishStatus string `json:"publish_status" gorm:"type:varchar(20);default:'pending'"` // pending, success, failed + PublishResponse *string `json:"publish_response" gorm:"type:text"` + 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()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} diff --git a/app/database/entity/campaign_destinations.entity.go b/app/database/entity/campaign_destinations.entity.go new file mode 100644 index 0000000..70ffd3d --- /dev/null +++ b/app/database/entity/campaign_destinations.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + "time" + "gorm.io/gorm" +) + +type CampaignDestinations struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + CampaignTypeID uint `json:"campaign_type_id" gorm:"type:int4"` + CampaignType CampaignTypes `json:"campaign_type" gorm:"foreignKey:CampaignTypeID;references:ID"` + Name string `json:"name" gorm:"type:varchar(150)"` + SubType *string `json:"sub_type" gorm:"type:varchar(100)"` + Description *string `json:"description" gorm:"type:text"` + URL *string `json:"url" gorm:"type:varchar(255)"` + 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()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} + diff --git a/app/database/entity/campaign_files.entity.go b/app/database/entity/campaign_files.entity.go new file mode 100644 index 0000000..ee41c10 --- /dev/null +++ b/app/database/entity/campaign_files.entity.go @@ -0,0 +1,22 @@ +package entity + +import ( + "time" + "gorm.io/gorm" +) + +type CampaignFiles struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + CampaignID uint `json:"campaign_id" gorm:"type:int4"` + Campaign Campaigns `json:"campaign" gorm:"foreignKey:CampaignID;references:ID"` + Type string `json:"type" gorm:"type:varchar(20)"` // url, file + FileURL *string `json:"file_url" gorm:"type:text"` + ExternalURL *string `json:"external_url" gorm:"type:text"` + IsDraft *bool `json:"is_draft" gorm:"type:bool;default:false"` + IsPublish *bool `json:"is_publish" 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()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} + diff --git a/app/database/entity/campaign_types.entity.go b/app/database/entity/campaign_types.entity.go new file mode 100644 index 0000000..2364dc2 --- /dev/null +++ b/app/database/entity/campaign_types.entity.go @@ -0,0 +1,17 @@ +package entity + +import ( + "time" + "gorm.io/gorm" +) + +type CampaignTypes struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name string `json:"name" gorm:"type:varchar(150)"` + Description *string `json:"description" gorm:"type:text"` + 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()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} + diff --git a/app/database/entity/campaigns.entity.go b/app/database/entity/campaigns.entity.go new file mode 100644 index 0000000..a7bd9aa --- /dev/null +++ b/app/database/entity/campaigns.entity.go @@ -0,0 +1,29 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" + "gorm.io/gorm" +) + +type Campaigns struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar(255)"` + CampaignTypeID uint `json:"campaign_type_id" gorm:"type:int4"` + CampaignType CampaignTypes `json:"campaign_type" gorm:"foreignKey:CampaignTypeID;references:ID"` + StartDate *time.Time `json:"start_date" gorm:"type:date"` + EndDate *time.Time `json:"end_date" gorm:"type:date"` + MediaTypeSelected *string `json:"media_type_selected" gorm:"type:varchar(100)"` + MediaItemSelected *string `json:"media_item_selected" gorm:"type:text"` // JSON array or comma-separated + Purpose *string `json:"purpose" gorm:"type:text"` + MediaPromote *bool `json:"media_promote" gorm:"type:bool;default:false"` + Description *string `json:"description" gorm:"type:text"` + CreatorID uint `json:"creator_id" gorm:"type:int4"` + Creator users.Users `json:"creator" gorm:"foreignKey:CreatorID;references:ID"` + Status string `json:"status" gorm:"type:varchar(50);default:'draft'"` // draft, waiting_approval, approved, rejected, published, archived + 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()"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` +} + diff --git a/app/database/entity/chat_messages.entity.go b/app/database/entity/chat_messages.entity.go new file mode 100644 index 0000000..3f0e36f --- /dev/null +++ b/app/database/entity/chat_messages.entity.go @@ -0,0 +1,24 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type ChatMessages struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"` + SenderID uint `json:"sender_id" gorm:"type:int4;not null;index"` + Message string `json:"message" gorm:"type:text;not null"` + MessageType string `json:"message_type" gorm:"type:varchar(20);not null;default:'text';check:message_type IN ('text', 'image', 'file', 'user', 'assistant')"` // 'text', 'image', 'file', 'user', 'assistant' + IsEdited bool `json:"is_edited" gorm:"default:false"` + EditedAt *time.Time `json:"edited_at"` + IsDeleted bool `json:"is_deleted" gorm:"default:false"` + DeletedAt *time.Time `json:"deleted_at"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relationships + ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"` + Sender *users.Users `json:"sender" gorm:"foreignKey:SenderID;references:ID"` +} diff --git a/app/database/entity/chat_participants.entity.go b/app/database/entity/chat_participants.entity.go new file mode 100644 index 0000000..f3e3f6d --- /dev/null +++ b/app/database/entity/chat_participants.entity.go @@ -0,0 +1,21 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type ChatParticipants struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"` + UserID uint `json:"user_id" gorm:"type:int4;not null;index"` + JoinedAt time.Time `json:"joined_at" gorm:"default:now()"` + LeftAt *time.Time `json:"left_at"` + IsActive bool `json:"is_active" gorm:"default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relationships + ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"` + User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` +} diff --git a/app/database/entity/chat_schedule_files.entity.go b/app/database/entity/chat_schedule_files.entity.go new file mode 100644 index 0000000..adc5d14 --- /dev/null +++ b/app/database/entity/chat_schedule_files.entity.go @@ -0,0 +1,23 @@ +package entity + +import ( + "time" +) + +type ChatScheduleFiles struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ChatScheduleID uint `json:"chat_schedule_id" gorm:"type:int4;not null;index"` + FileName string `json:"file_name" gorm:"type:varchar(255);not null"` + OriginalName string `json:"original_name" gorm:"type:varchar(255);not null"` + FilePath string `json:"file_path" gorm:"type:varchar(500);not null"` + FileSize int64 `json:"file_size" gorm:"type:int8"` + MimeType string `json:"mime_type" gorm:"type:varchar(100)"` + FileType string `json:"file_type" gorm:"type:varchar(20);not null;check:file_type IN ('article', 'journal', 'video', 'audio', 'document', 'other')"` + Description string `json:"description" gorm:"type:text"` + IsRequired bool `json:"is_required" gorm:"default:false"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relationships + ChatSchedule *ChatSchedules `json:"chat_schedule" gorm:"foreignKey:ChatScheduleID;references:ID"` +} diff --git a/app/database/entity/chat_schedules.entity.go b/app/database/entity/chat_schedules.entity.go new file mode 100644 index 0000000..f3036c8 --- /dev/null +++ b/app/database/entity/chat_schedules.entity.go @@ -0,0 +1,27 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type ChatSchedules struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"` + Title string `json:"title" gorm:"type:varchar(255);not null"` + Description string `json:"description" gorm:"type:text"` + Summary string `json:"summary" gorm:"type:text"` + ScheduledAt time.Time `json:"scheduled_at" gorm:"not null"` + Duration int `json:"duration" gorm:"type:int4;default:60"` // duration in minutes + Status string `json:"status" gorm:"type:varchar(20);not null;default:'scheduled';check:status IN ('scheduled', 'ongoing', 'completed', 'cancelled')"` + IsReminderSent bool `json:"is_reminder_sent" gorm:"default:false"` + ReminderSentAt *time.Time `json:"reminder_sent_at"` + CreatedBy uint `json:"created_by" gorm:"type:int4;not null;index"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relationships + ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"` + Creator *users.Users `json:"creator" gorm:"foreignKey:CreatedBy;references:ID"` + Files []*ChatScheduleFiles `json:"files" gorm:"foreignKey:ChatScheduleID;references:ID"` +} diff --git a/app/database/entity/chat_sessions.entity.go b/app/database/entity/chat_sessions.entity.go new file mode 100644 index 0000000..9d5ecac --- /dev/null +++ b/app/database/entity/chat_sessions.entity.go @@ -0,0 +1,20 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type ChatSessions struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Name *string `json:"name" gorm:"type:varchar(255)"` // null for personal chat, filled for group chat + Type string `json:"type" gorm:"type:varchar(20);not null;default:'personal'"` // 'personal' or 'group' + CreatedBy uint `json:"created_by" gorm:"type:int4;not null;index"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + + // Relationships + Creator *users.Users `json:"creator" gorm:"foreignKey:CreatedBy;references:ID"` + Participants []*ChatParticipants `json:"participants" gorm:"foreignKey:ChatSessionID;references:ID"` + Messages []*ChatMessages `json:"messages" gorm:"foreignKey:ChatSessionID;references:ID"` +} diff --git a/app/database/entity/cities.entity.go b/app/database/entity/cities.entity.go new file mode 100644 index 0000000..bdb0a80 --- /dev/null +++ b/app/database/entity/cities.entity.go @@ -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"` +} diff --git a/app/database/entity/conversations.entity.go b/app/database/entity/conversations.entity.go new file mode 100644 index 0000000..21a8d73 --- /dev/null +++ b/app/database/entity/conversations.entity.go @@ -0,0 +1,17 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type Conversations struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Participant1ID uint `json:"participant1_id" gorm:"type:int4;not null;index"` + Participant2ID uint `json:"participant2_id" gorm:"type:int4;not null;index"` + LastMessageAt *time.Time `json:"last_message_at"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + Participant1 *users.Users `json:"participant1" gorm:"foreignKey:Participant1ID;references:ID"` + Participant2 *users.Users `json:"participant2" gorm:"foreignKey:Participant2ID;references:ID"` +} diff --git a/app/database/entity/csrf_token_records.entity.go b/app/database/entity/csrf_token_records.entity.go new file mode 100644 index 0000000..fee0a14 --- /dev/null +++ b/app/database/entity/csrf_token_records.entity.go @@ -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 +} diff --git a/app/database/entity/custom_static_pages.entity.go b/app/database/entity/custom_static_pages.entity.go new file mode 100644 index 0000000..95b86f8 --- /dev/null +++ b/app/database/entity/custom_static_pages.entity.go @@ -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()"` +} diff --git a/app/database/entity/districts.entity.go b/app/database/entity/districts.entity.go new file mode 100644 index 0000000..480de99 --- /dev/null +++ b/app/database/entity/districts.entity.go @@ -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"` +} diff --git a/app/database/entity/ebook_purchases.entity.go b/app/database/entity/ebook_purchases.entity.go new file mode 100644 index 0000000..8aa4b02 --- /dev/null +++ b/app/database/entity/ebook_purchases.entity.go @@ -0,0 +1,26 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" +) + +type EbookPurchases struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + BuyerId uint `json:"buyer_id" gorm:"type:int4"` + Buyer *users.Users `json:"buyer" gorm:"foreignKey:BuyerId;references:ID"` + EbookId uint `json:"ebook_id" gorm:"type:int4"` + Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"` + PurchasePrice float64 `json:"purchase_price" gorm:"type:decimal(10,2)"` + PaymentMethod *string `json:"payment_method" gorm:"type:varchar"` + PaymentStatus *string `json:"payment_status" gorm:"type:varchar;default:'pending'"` + TransactionId *string `json:"transaction_id" gorm:"type:varchar"` + PaymentProof *string `json:"payment_proof" gorm:"type:varchar"` + PaymentDate *time.Time `json:"payment_date" gorm:"type:timestamp"` + DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"` + LastDownloadAt *time.Time `json:"last_download_at" gorm:"type:timestamp"` + StatusId *int `json:"status_id" gorm:"type:int4;default:1"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/ebook_ratings.entity.go b/app/database/entity/ebook_ratings.entity.go new file mode 100644 index 0000000..4706e65 --- /dev/null +++ b/app/database/entity/ebook_ratings.entity.go @@ -0,0 +1,24 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" +) + +type EbookRatings struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserId uint `json:"user_id" gorm:"type:int4"` + User *users.Users `json:"user" gorm:"foreignKey:UserId;references:ID"` + EbookId uint `json:"ebook_id" gorm:"type:int4"` + Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"` + PurchaseId uint `json:"purchase_id" gorm:"type:int4"` + Purchase *EbookPurchases `json:"purchase" gorm:"foreignKey:PurchaseId;references:ID"` + Rating int `json:"rating" gorm:"type:int4;check:rating >= 1 AND rating <= 5"` + Review *string `json:"review" gorm:"type:text"` + IsAnonymous *bool `json:"is_anonymous" gorm:"type:bool;default:false"` + IsVerified *bool `json:"is_verified" gorm:"type:bool;default:true"` + StatusId *int `json:"status_id" gorm:"type:int4;default:1"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/ebook_wishlists.entity.go b/app/database/entity/ebook_wishlists.entity.go new file mode 100644 index 0000000..02329b1 --- /dev/null +++ b/app/database/entity/ebook_wishlists.entity.go @@ -0,0 +1,16 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" +) + +type EbookWishlists struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserId uint `json:"user_id" gorm:"type:int4"` + User *users.Users `json:"user" gorm:"foreignKey:UserId;references:ID"` + EbookId uint `json:"ebook_id" gorm:"type:int4"` + Ebook *Ebooks `json:"ebook" gorm:"foreignKey:EbookId;references:ID"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/ebooks.entity.go b/app/database/entity/ebooks.entity.go new file mode 100644 index 0000000..5ffcf0d --- /dev/null +++ b/app/database/entity/ebooks.entity.go @@ -0,0 +1,40 @@ +package entity + +import ( + users "campaign-pool-be/app/database/entity/users" + "time" +) + +type Ebooks struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + Title string `json:"title" gorm:"type:varchar"` + Slug string `json:"slug" gorm:"type:varchar"` + Description string `json:"description" gorm:"type:varchar"` + Price float64 `json:"price" gorm:"type:decimal(10,2)"` + PdfFilePath *string `json:"pdf_file_path" gorm:"type:varchar"` + PdfFileName *string `json:"pdf_file_name" gorm:"type:varchar"` + PdfFileSize *int64 `json:"pdf_file_size" gorm:"type:int8"` + ThumbnailPath *string `json:"thumbnail_path" gorm:"type:varchar"` + ThumbnailName *string `json:"thumbnail_name" gorm:"type:varchar"` + AuthorId uint `json:"author_id" gorm:"type:int4"` + Author *users.Users `json:"author" gorm:"foreignKey:AuthorId;references:ID"` + Category *string `json:"category" gorm:"type:varchar"` + Tags *string `json:"tags" gorm:"type:varchar"` + PageCount *int `json:"page_count" gorm:"type:int4"` + Language *string `json:"language" gorm:"type:varchar;default:'id'"` + Isbn *string `json:"isbn" gorm:"type:varchar"` + Publisher *string `json:"publisher" gorm:"type:varchar"` + PublishedYear *int `json:"published_year" gorm:"type:int4"` + DownloadCount *int `json:"download_count" gorm:"type:int4;default:0"` + PurchaseCount *int `json:"purchase_count" gorm:"type:int4;default:0"` + WishlistCount *int `json:"wishlist_count" gorm:"type:int4;default:0"` + Rating *float64 `json:"rating" gorm:"type:decimal(3,2);default:0"` + ReviewCount *int `json:"review_count" gorm:"type:int4;default:0"` + StatusId *int `json:"status_id" gorm:"type:int4;default:1"` + IsActive *bool `json:"is_active" gorm:"type:bool;default:true"` + IsPublished *bool `json:"is_published" gorm:"type:bool;default:false"` + PublishedAt *time.Time `json:"published_at" gorm:"type:timestamp"` + CreatedById *uint `json:"created_by_id" gorm:"type:int4"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` +} diff --git a/app/database/entity/education_history.entity.go b/app/database/entity/education_history.entity.go new file mode 100644 index 0000000..8943338 --- /dev/null +++ b/app/database/entity/education_history.entity.go @@ -0,0 +1,19 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type EducationHistory struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserID uint `json:"user_id" gorm:"type:int4;not null;index"` + SchoolName string `json:"school_name" gorm:"type:varchar;not null"` + Major string `json:"major" gorm:"type:varchar;not null"` + EducationLevel string `json:"education_level" gorm:"type:varchar;not null"` + GraduationYear int `json:"graduation_year" gorm:"type:int4;not null"` + CertificateImage *string `json:"certificate_image" gorm:"type:varchar"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` +} diff --git a/app/database/entity/feedbacks.entity.go b/app/database/entity/feedbacks.entity.go new file mode 100644 index 0000000..c735a7f --- /dev/null +++ b/app/database/entity/feedbacks.entity.go @@ -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 diff --git a/app/database/entity/forgot_passwords.entity.go b/app/database/entity/forgot_passwords.entity.go new file mode 100644 index 0000000..368d0e5 --- /dev/null +++ b/app/database/entity/forgot_passwords.entity.go @@ -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()"` +} diff --git a/app/database/entity/magazine_files.entity.go b/app/database/entity/magazine_files.entity.go new file mode 100644 index 0000000..c16ad49 --- /dev/null +++ b/app/database/entity/magazine_files.entity.go @@ -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()"` +} diff --git a/app/database/entity/magazines.entity.go b/app/database/entity/magazines.entity.go new file mode 100644 index 0000000..3cfc206 --- /dev/null +++ b/app/database/entity/magazines.entity.go @@ -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()"` +} diff --git a/app/database/entity/master_approval_statuses.entity.go b/app/database/entity/master_approval_statuses.entity.go new file mode 100644 index 0000000..7a24565 --- /dev/null +++ b/app/database/entity/master_approval_statuses.entity.go @@ -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"` +} diff --git a/app/database/entity/master_menus.entity.go b/app/database/entity/master_menus.entity.go new file mode 100644 index 0000000..143e764 --- /dev/null +++ b/app/database/entity/master_menus.entity.go @@ -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()"` +} diff --git a/app/database/entity/master_modules.entity.go b/app/database/entity/master_modules.entity.go new file mode 100644 index 0000000..0d39445 --- /dev/null +++ b/app/database/entity/master_modules.entity.go @@ -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()"` +} diff --git a/app/database/entity/master_statuses.entity.go b/app/database/entity/master_statuses.entity.go new file mode 100644 index 0000000..75d20a0 --- /dev/null +++ b/app/database/entity/master_statuses.entity.go @@ -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"` +} diff --git a/app/database/entity/one_time_passwords.entity.go b/app/database/entity/one_time_passwords.entity.go new file mode 100644 index 0000000..2475fa4 --- /dev/null +++ b/app/database/entity/one_time_passwords.entity.go @@ -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()"` +} diff --git a/app/database/entity/provinces.entity.go b/app/database/entity/provinces.entity.go new file mode 100644 index 0000000..f9f3902 --- /dev/null +++ b/app/database/entity/provinces.entity.go @@ -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"` +} diff --git a/app/database/entity/research_journals.entity.go b/app/database/entity/research_journals.entity.go new file mode 100644 index 0000000..f21409e --- /dev/null +++ b/app/database/entity/research_journals.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type ResearchJournals struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserID uint `json:"user_id" gorm:"type:int4;not null;index"` + JournalTitle string `json:"journal_title" gorm:"type:varchar;not null"` + Publisher string `json:"publisher" gorm:"type:varchar;not null"` + JournalURL string `json:"journal_url" gorm:"type:varchar;not null"` + PublishedDate *time.Time `json:"published_date"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` +} diff --git a/app/database/entity/subscription.entity.go b/app/database/entity/subscription.entity.go new file mode 100644 index 0000000..710cbf4 --- /dev/null +++ b/app/database/entity/subscription.entity.go @@ -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()"` +} diff --git a/app/database/entity/user_levels/user_levels.entity.go b/app/database/entity/user_levels/user_levels.entity.go new file mode 100644 index 0000000..f95ddfa --- /dev/null +++ b/app/database/entity/user_levels/user_levels.entity.go @@ -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()"` +} diff --git a/app/database/entity/user_levels/user_levels.entity.go~ b/app/database/entity/user_levels/user_levels.entity.go~ new file mode 100644 index 0000000..dbbea4d --- /dev/null +++ b/app/database/entity/user_levels/user_levels.entity.go~ @@ -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()"` +} diff --git a/app/database/entity/user_role_accesses.entity.go b/app/database/entity/user_role_accesses.entity.go new file mode 100644 index 0000000..47d94df --- /dev/null +++ b/app/database/entity/user_role_accesses.entity.go @@ -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()"` +} diff --git a/app/database/entity/user_role_level_details.entity.go b/app/database/entity/user_role_level_details.entity.go new file mode 100644 index 0000000..f187c06 --- /dev/null +++ b/app/database/entity/user_role_level_details.entity.go @@ -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()"` +} diff --git a/app/database/entity/user_roles.entity.go b/app/database/entity/user_roles.entity.go new file mode 100644 index 0000000..8795923 --- /dev/null +++ b/app/database/entity/user_roles.entity.go @@ -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()"` +} diff --git a/app/database/entity/users/users.entity.go b/app/database/entity/users/users.entity.go new file mode 100644 index 0000000..1251f16 --- /dev/null +++ b/app/database/entity/users/users.entity.go @@ -0,0 +1,38 @@ +package users + +import ( + userLevels "campaign-pool-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()"` +} diff --git a/app/database/entity/work_history.entity.go b/app/database/entity/work_history.entity.go new file mode 100644 index 0000000..c22f163 --- /dev/null +++ b/app/database/entity/work_history.entity.go @@ -0,0 +1,18 @@ +package entity + +import ( + "campaign-pool-be/app/database/entity/users" + "time" +) + +type WorkHistory struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserID uint `json:"user_id" gorm:"type:int4;not null;index"` + JobTitle string `json:"job_title" gorm:"type:varchar;not null"` + CompanyName string `json:"company_name" gorm:"type:varchar;not null"` + StartDate time.Time `json:"start_date" gorm:"not null"` + EndDate *time.Time `json:"end_date"` + CreatedAt time.Time `json:"created_at" gorm:"default:now()"` + UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` + User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` +} diff --git a/app/database/index.database.go b/app/database/index.database.go new file mode 100644 index 0000000..c229cc2 --- /dev/null +++ b/app/database/index.database.go @@ -0,0 +1,170 @@ +package database + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/database/entity/article_category_details" + "campaign-pool-be/app/database/entity/user_levels" + "campaign-pool-be/app/database/entity/users" + "campaign-pool-be/config/config" + + "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.ChatParticipants{}, + entity.ChatSessions{}, + entity.ChatSchedules{}, + entity.ChatScheduleFiles{}, + entity.AIChatSessions{}, + entity.AIChatMessages{}, + entity.AIChatLogs{}, + + // Ebook entities + entity.Ebooks{}, + entity.EbookWishlists{}, + entity.EbookPurchases{}, + entity.EbookRatings{}, + + // Campaign entities + entity.CampaignTypes{}, + entity.CampaignDestinations{}, + entity.Campaigns{}, + entity.CampaignFiles{}, + entity.CampaignDestinationRelations{}, + entity.CampaignApprovals{}, + entity.CampaignChats{}, + } +} + +// 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!") +} diff --git a/app/database/seeds/activity_log_types.seeds.go b/app/database/seeds/activity_log_types.seeds.go new file mode 100644 index 0000000..cef55ac --- /dev/null +++ b/app/database/seeds/activity_log_types.seeds.go @@ -0,0 +1,51 @@ +package seeds + +import ( + "campaign-pool-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 +} diff --git a/app/database/seeds/master_approval_statuses.seeds.go b/app/database/seeds/master_approval_statuses.seeds.go new file mode 100644 index 0000000..a0164ec --- /dev/null +++ b/app/database/seeds/master_approval_statuses.seeds.go @@ -0,0 +1,46 @@ +package seeds + +import ( + "campaign-pool-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 +} diff --git a/app/database/seeds/master_statuses.seeds.go b/app/database/seeds/master_statuses.seeds.go new file mode 100644 index 0000000..8ac0c6c --- /dev/null +++ b/app/database/seeds/master_statuses.seeds.go @@ -0,0 +1,46 @@ +package seeds + +import ( + "campaign-pool-be/app/database/entity" + + "gorm.io/gorm" +) + +type MasterStatusesSeeder struct{} + +var masterStatuses = []entity.MasterStatuses{ + { + ID: 1, + Name: "Waiting", + IsActive: true, + }, + { + ID: 2, + Name: "Active", + IsActive: true, + }, + { + ID: 3, + Name: "Inactive", + IsActive: true, + }, +} + +func (MasterStatusesSeeder) Seed(conn *gorm.DB) error { + for _, row := range masterStatuses { + if err := conn.Create(&row).Error; err != nil { + return err + } + } + + return nil +} + +func (MasterStatusesSeeder) Count(conn *gorm.DB) (int, error) { + var count int64 + if err := conn.Model(&entity.MasterStatuses{}).Count(&count).Error; err != nil { + return 0, err + } + + return int(count), nil +} diff --git a/app/middleware/audit_trails.middleware.go b/app/middleware/audit_trails.middleware.go new file mode 100644 index 0000000..05babc2 --- /dev/null +++ b/app/middleware/audit_trails.middleware.go @@ -0,0 +1,106 @@ +package middleware + +import ( + "encoding/json" + "log" + "campaign-pool-be/app/database/entity" + utilSvc "campaign-pool-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) + + // Execute the next handler + err := c.Next() + + // Get status code - ensure it's set correctly for errors + statusCode := c.Response().StatusCode() + if err != nil { + // If error occurred, ensure status code reflects the error + // The error handler should have set this, but if not, default to 500 + if statusCode == fiber.StatusOK || statusCode == 0 { + statusCode = fiber.StatusInternalServerError + } + } + + // Get response body + responseBody := c.Response().Body() + + // If response body is empty and there's an error, create error response + if len(responseBody) == 0 && err != nil { + // Create error response JSON matching the error handler format + errorResp := map[string]interface{}{ + "success": false, + "code": statusCode, + "message": err.Error(), + } + if errorJSON, marshalErr := json.Marshal(errorResp); marshalErr == nil { + responseBody = errorJSON + } else { + responseBody = []byte(err.Error()) + } + } + + audit := entity.AuditTrails{ + Method: c.Method(), + Path: c.OriginalURL(), + IP: getIP(c), + Status: statusCode, + UserID: userId, + RequestHeaders: string(headersJSON), + RequestBody: string(requestBody), + ResponseBody: string(responseBody), + DurationMs: time.Since(start).Milliseconds(), + CreatedAt: time.Now(), + } + + // Save audit trail - use goroutine to avoid blocking + // IMPORTANT: Save synchronously to ensure it completes even if app crashes + // Using goroutine but with proper error handling + go func(auditRecord entity.AuditTrails) { + if saveErr := db.Create(&auditRecord).Error; saveErr != nil { + log.Printf("Failed to save audit trail for %s %s (status: %d): %v", + auditRecord.Method, auditRecord.Path, auditRecord.Status, saveErr) + } + }(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 +} diff --git a/app/middleware/csrf.middleware.go b/app/middleware/csrf.middleware.go new file mode 100644 index 0000000..0cfe90b --- /dev/null +++ b/app/middleware/csrf.middleware.go @@ -0,0 +1,79 @@ +package middleware + +import ( + "campaign-pool-be/app/database/entity" + "fmt" + "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 +} diff --git a/app/middleware/register.middleware.go b/app/middleware/register.middleware.go new file mode 100644 index 0000000..1a204b7 --- /dev/null +++ b/app/middleware/register.middleware.go @@ -0,0 +1,168 @@ +package middleware + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/config/config" + utilsSvc "campaign-pool-be/utils" + "log" + "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://campaignpool.id", + 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 + //=============================== + + // Only setup CSRF middleware if enabled + if m.Cfg.Middleware.Csrf.Enable { + // 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{ + 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 (only available if CSRF is enabled) + 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, + }) + }) +} diff --git a/app/module/activity_logs/activity_logs.module.go b/app/module/activity_logs/activity_logs.module.go new file mode 100644 index 0000000..14c9eff --- /dev/null +++ b/app/module/activity_logs/activity_logs.module.go @@ -0,0 +1,55 @@ +package activity_logs + +import ( + "campaign-pool-be/app/module/activity_logs/controller" + "campaign-pool-be/app/module/activity_logs/repository" + "campaign-pool-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) + }) +} diff --git a/app/module/activity_logs/controller/activity_logs.controller.go b/app/module/activity_logs/controller/activity_logs.controller.go new file mode 100644 index 0000000..f52c25f --- /dev/null +++ b/app/module/activity_logs/controller/activity_logs.controller.go @@ -0,0 +1,244 @@ +package controller + +import ( + "campaign-pool-be/app/module/activity_logs/request" + "campaign-pool-be/app/module/activity_logs/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-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 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 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-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @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-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-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 +// @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 +} diff --git a/app/module/activity_logs/controller/controller.go b/app/module/activity_logs/controller/controller.go new file mode 100644 index 0000000..4d7c766 --- /dev/null +++ b/app/module/activity_logs/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-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), + } +} diff --git a/app/module/activity_logs/mapper/activity_logs.mapper.go b/app/module/activity_logs/mapper/activity_logs.mapper.go new file mode 100644 index 0000000..2407683 --- /dev/null +++ b/app/module/activity_logs/mapper/activity_logs.mapper.go @@ -0,0 +1,20 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-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 +} diff --git a/app/module/activity_logs/repository/activity_logs.repository.go b/app/module/activity_logs/repository/activity_logs.repository.go new file mode 100644 index 0000000..7d28b13 --- /dev/null +++ b/app/module/activity_logs/repository/activity_logs.repository.go @@ -0,0 +1,124 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/activity_logs/request" + "campaign-pool-be/utils/paginator" + "fmt" + "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 +} diff --git a/app/module/activity_logs/request/activity_logs.request.go b/app/module/activity_logs/request/activity_logs.request.go new file mode 100644 index 0000000..661747f --- /dev/null +++ b/app/module/activity_logs/request/activity_logs.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-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 +} diff --git a/app/module/activity_logs/response/activity_logs.response.go b/app/module/activity_logs/response/activity_logs.response.go new file mode 100644 index 0000000..6c2040e --- /dev/null +++ b/app/module/activity_logs/response/activity_logs.response.go @@ -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"` +} diff --git a/app/module/activity_logs/service/activity_logs.service.go b/app/module/activity_logs/service/activity_logs.service.go new file mode 100644 index 0000000..a1302f0 --- /dev/null +++ b/app/module/activity_logs/service/activity_logs.service.go @@ -0,0 +1,124 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/activity_logs/mapper" + "campaign-pool-be/app/module/activity_logs/repository" + "campaign-pool-be/app/module/activity_logs/request" + "campaign-pool-be/app/module/activity_logs/response" + "campaign-pool-be/app/module/articles/service" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + + "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 +} diff --git a/app/module/advertisement/advertisement.module.go b/app/module/advertisement/advertisement.module.go new file mode 100644 index 0000000..22e7a82 --- /dev/null +++ b/app/module/advertisement/advertisement.module.go @@ -0,0 +1,57 @@ +package advertisement + +import ( + "campaign-pool-be/app/module/advertisement/controller" + "campaign-pool-be/app/module/advertisement/repository" + "campaign-pool-be/app/module/advertisement/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of AdvertisementRouter +type AdvertisementRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Advertisement module +var NewAdvertisementModule = fx.Options( + // register repository of Advertisement module + fx.Provide(repository.NewAdvertisementRepository), + + // register service of Advertisement module + fx.Provide(service.NewAdvertisementService), + + // register controller of Advertisement module + fx.Provide(controller.NewController), + + // register router of Advertisement module + fx.Provide(NewAdvertisementRouter), +) + +// init AdvertisementRouter +func NewAdvertisementRouter(fiber *fiber.App, controller *controller.Controller) *AdvertisementRouter { + return &AdvertisementRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Advertisement module +func (_i *AdvertisementRouter) RegisterAdvertisementRoutes() { + // define controllers + advertisementController := _i.Controller.Advertisement + + // define routes + _i.App.Route("/advertisement", func(router fiber.Router) { + router.Get("/", advertisementController.All) + router.Get("/:id", advertisementController.Show) + router.Post("/", advertisementController.Save) + router.Post("/upload/:id", advertisementController.Upload) + router.Get("/viewer/:filename", advertisementController.Viewer) + router.Put("/:id", advertisementController.Update) + router.Put("/publish/:id", advertisementController.UpdatePublish) + router.Delete("/:id", advertisementController.Delete) + }) +} diff --git a/app/module/advertisement/controller/advertisement.controller.go b/app/module/advertisement/controller/advertisement.controller.go new file mode 100644 index 0000000..03eeac3 --- /dev/null +++ b/app/module/advertisement/controller/advertisement.controller.go @@ -0,0 +1,284 @@ +package controller + +import ( + "campaign-pool-be/app/module/advertisement/request" + "campaign-pool-be/app/module/advertisement/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type advertisementController struct { + advertisementService service.AdvertisementService + Log zerolog.Logger +} + +type AdvertisementController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Upload(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + UpdatePublish(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewAdvertisementController(advertisementService service.AdvertisementService, log zerolog.Logger) AdvertisementController { + return &advertisementController{ + advertisementService: advertisementService, + Log: log, + } +} + +// All get all Advertisement +// @Summary Get all Advertisement +// @Description API for getting all Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param req query request.AdvertisementQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement [get] +func (_i *advertisementController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.AdvertisementQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + RedirectLink: c.Query("redirectLink"), + Placement: c.Query("placement"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + advertisementData, paging, err := _i.advertisementService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement list successfully retrieved"}, + Data: advertisementData, + Meta: paging, + }) +} + +// Show get one Advertisement +// @Summary Get one Advertisement +// @Description API for getting one Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/{id} [get] +func (_i *advertisementController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + advertisementData, err := _i.advertisementService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully retrieved"}, + Data: advertisementData, + }) +} + +// Save create Advertisement +// @Summary Create Advertisement +// @Description API for create Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.AdvertisementCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement [post] +func (_i *advertisementController) Save(c *fiber.Ctx) error { + req := new(request.AdvertisementCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.advertisementService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully created"}, + Data: dataResult, + }) +} + +// Upload Advertisement +// @Summary Upload Advertisement +// @Description API for Upload File Advertisement +// @Tags Advertisement +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param file formData file true "Upload file" multiple false +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/upload/{id} [post] +func (_i *advertisementController) Upload(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.advertisementService.Upload(c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully upload"}, + }) +} + +// Update update Advertisement +// @Summary update Advertisement +// @Description API for update Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.AdvertisementUpdateRequest true "Required payload" +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/{id} [put] +func (_i *advertisementController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.AdvertisementUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.advertisementService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully updated"}, + }) +} + +// UpdatePublish Advertisement +// @Summary Update Publish Advertisement +// @Description API for Update Publish Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Advertisement ID" +// @Param isPublish query bool true "Advertisement Publish Status" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/publish/{id} [put] +func (_i *advertisementController) UpdatePublish(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + isPublish, err := strconv.ParseBool(c.Query("isPublish")) + if err != nil { + return err + } + + err = _i.advertisementService.UpdatePublish(uint(id), isPublish) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully publish updated"}, + }) +} + +// Delete delete Advertisement +// @Summary delete Advertisement +// @Description API for delete Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Advertisement ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/{id} [delete] +func (_i *advertisementController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.advertisementService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Advertisement successfully deleted"}, + }) +} + +// Viewer Advertisement +// @Summary Viewer Advertisement +// @Description API for Viewer Advertisement +// @Tags Advertisement +// @Security Bearer +// @Param filename path string true "Content File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /advertisement/viewer/{filename} [get] +func (_i *advertisementController) Viewer(c *fiber.Ctx) error { + return _i.advertisementService.Viewer(c) +} diff --git a/app/module/advertisement/controller/controller.go b/app/module/advertisement/controller/controller.go new file mode 100644 index 0000000..d0f2510 --- /dev/null +++ b/app/module/advertisement/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/advertisement/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + Advertisement AdvertisementController +} + +func NewController(AdvertisementService service.AdvertisementService, log zerolog.Logger) *Controller { + return &Controller{ + Advertisement: NewAdvertisementController(AdvertisementService, log), + } +} diff --git a/app/module/advertisement/mapper/advertisement.mapper.go b/app/module/advertisement/mapper/advertisement.mapper.go new file mode 100644 index 0000000..c17b11a --- /dev/null +++ b/app/module/advertisement/mapper/advertisement.mapper.go @@ -0,0 +1,28 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/advertisement/response" +) + +func AdvertisementResponseMapper(advertisementReq *entity.Advertisement, host string) (advertisementRes *res.AdvertisementResponse) { + if advertisementReq != nil { + advertisementRes = &res.AdvertisementResponse{ + ID: advertisementReq.ID, + Title: advertisementReq.Title, + Description: advertisementReq.Description, + RedirectLink: advertisementReq.RedirectLink, + Placement: advertisementReq.Placement, + StatusId: advertisementReq.StatusId, + IsActive: advertisementReq.IsActive, + IsPublish: advertisementReq.IsPublish, + CreatedAt: advertisementReq.CreatedAt, + UpdatedAt: advertisementReq.UpdatedAt, + } + + if advertisementReq.ContentFilePath != nil { + advertisementRes.ContentFileUrl = host + "/advertisement/viewer/" + *advertisementReq.ContentFileName + } + } + return advertisementRes +} diff --git a/app/module/advertisement/repository/advertisement.repository.go b/app/module/advertisement/repository/advertisement.repository.go new file mode 100644 index 0000000..59ff02a --- /dev/null +++ b/app/module/advertisement/repository/advertisement.repository.go @@ -0,0 +1,124 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/advertisement/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type advertisementRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// AdvertisementRepository define interface of IAdvertisementRepository +type AdvertisementRepository interface { + GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error) + FindOne(id uint) (advertisement *entity.Advertisement, err error) + FindByFilename(contentFilename string) (advertisement *entity.Advertisement, err error) + Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) + Update(id uint, advertisement *entity.Advertisement) (err error) + Delete(id uint) (err error) +} + +func NewAdvertisementRepository(db *database.Database, logger zerolog.Logger) AdvertisementRepository { + return &advertisementRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IAdvertisementRepository +func (_i *advertisementRepository) GetAll(req request.AdvertisementQueryRequest) (advertisements []*entity.Advertisement, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Advertisement{}) + + query = query.Where("is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.RedirectLink != nil && *req.RedirectLink != "" { + redirectLink := strings.ToLower(*req.RedirectLink) + query = query.Where("LOWER(redirect_link) LIKE ?", "%"+strings.ToLower(redirectLink)+"%") + } + if req.Placement != nil && *req.Placement != "" { + placement := strings.ToLower(*req.Placement) + query = query.Where("LOWER(placement) LIKE ?", "%"+strings.ToLower(placement)+"%") + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&advertisements).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *advertisementRepository) FindOne(id uint) (advertisement *entity.Advertisement, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.First(&advertisement).Error; err != nil { + return nil, err + } + + return advertisement, nil +} + +func (_i *advertisementRepository) FindByFilename(contentFilename string) (advertisement *entity.Advertisement, err error) { + query := _i.DB.DB.Where("content_file_name = ?", contentFilename) + + if err := query.First(&advertisement).Error; err != nil { + return nil, err + } + + return advertisement, nil +} + +func (_i *advertisementRepository) Create(advertisement *entity.Advertisement) (advertisementReturn *entity.Advertisement, err error) { + result := _i.DB.DB.Create(advertisement) + return advertisement, result.Error +} + +func (_i *advertisementRepository) Update(id uint, advertisement *entity.Advertisement) (err error) { + advertisementMap, err := utilSvc.StructToMap(advertisement) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Advertisement{}).Where(&entity.Advertisement{ID: id}) + return query.Updates(advertisementMap).Error +} + +func (_i *advertisementRepository) Delete(id uint) error { + query := _i.DB.DB.Model(&entity.Advertisement{}).Where("id = ?", id) + return query.Delete(&entity.Advertisement{}).Error +} diff --git a/app/module/advertisement/request/advertisement.request.go b/app/module/advertisement/request/advertisement.request.go new file mode 100644 index 0000000..68f07e0 --- /dev/null +++ b/app/module/advertisement/request/advertisement.request.go @@ -0,0 +1,100 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type AdvertisementGeneric interface { + ToEntity() +} + +type AdvertisementQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + RedirectLink *string `json:"redirectLink"` + Placement *string `json:"placement"` + IsPublish *bool `json:"isPublish"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type AdvertisementCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + RedirectLink string `json:"redirectLink" validate:"required"` + Placement string `json:"placement" validate:"required"` +} + +func (req AdvertisementCreateRequest) ToEntity() *entity.Advertisement { + return &entity.Advertisement{ + Title: req.Title, + Description: req.Description, + RedirectLink: req.RedirectLink, + Placement: req.Placement, + StatusId: 1, + IsPublish: true, + IsActive: true, + } +} + +type AdvertisementUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + RedirectLink string `json:"redirectLink" validate:"required"` + Placement string `json:"placement" validate:"required"` +} + +func (req AdvertisementUpdateRequest) ToEntity() *entity.Advertisement { + return &entity.Advertisement{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + RedirectLink: req.RedirectLink, + Placement: req.Placement, + UpdatedAt: time.Now(), + } +} + +type AdvertisementQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + RedirectLink string `json:"redirectLink"` + Placement string `json:"placement"` + StatusId string `json:"statusId"` + IsPublish string `json:"isPublish"` +} + +func (req AdvertisementQueryRequestContext) ToParamRequest() AdvertisementQueryRequest { + var request AdvertisementQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if redirectLink := req.RedirectLink; redirectLink != "" { + request.RedirectLink = &redirectLink + } + if placement := req.Placement; placement != "" { + request.Placement = &placement + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/advertisement/response/advertisement.response.go b/app/module/advertisement/response/advertisement.response.go new file mode 100644 index 0000000..55db4aa --- /dev/null +++ b/app/module/advertisement/response/advertisement.response.go @@ -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"` +} diff --git a/app/module/advertisement/service/advertisement.service.go b/app/module/advertisement/service/advertisement.service.go new file mode 100644 index 0000000..aa8cc6e --- /dev/null +++ b/app/module/advertisement/service/advertisement.service.go @@ -0,0 +1,264 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/advertisement/mapper" + "campaign-pool-be/app/module/advertisement/repository" + "campaign-pool-be/app/module/advertisement/request" + "campaign-pool-be/app/module/advertisement/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + "context" + "fmt" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// AdvertisementService +type advertisementService struct { + Repo repository.AdvertisementRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage +} + +// AdvertisementService define interface of IAdvertisementService +type AdvertisementService interface { + All(req request.AdvertisementQueryRequest) (advertisement []*response.AdvertisementResponse, paging paginator.Pagination, err error) + Show(id uint) (advertisement *response.AdvertisementResponse, err error) + Save(req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error) + Upload(c *fiber.Ctx, id uint) (err error) + Update(id uint, req request.AdvertisementUpdateRequest) (err error) + UpdatePublish(id uint, isPublish bool) (err error) + Delete(id uint) error + Viewer(c *fiber.Ctx) (err error) +} + +// NewAdvertisementService init AdvertisementService +func NewAdvertisementService(repo repository.AdvertisementRepository, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) AdvertisementService { + + return &advertisementService{ + Repo: repo, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of AdvertisementService +func (_i *advertisementService) All(req request.AdvertisementQueryRequest) (advertisements []*response.AdvertisementResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + for _, result := range results { + advertisements = append(advertisements, mapper.AdvertisementResponseMapper(result, host)) + } + + return +} + +func (_i *advertisementService) Show(id uint) (advertisement *response.AdvertisementResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + return mapper.AdvertisementResponseMapper(result, host), nil +} + +func (_i *advertisementService) Save(req request.AdvertisementCreateRequest) (advertisement *entity.Advertisement, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + return _i.Repo.Create(newReq) +} + +func (_i *advertisementService) Upload(c *fiber.Ctx, id uint) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + + result, err := _i.Repo.FindOne(id) + + if result == nil { + // Return status 400. Id not found. + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("advertisement/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + + result.ContentFileName = &newFilename + result.ContentFilePath = &objectName + + err = _i.Repo.Update(id, result) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + return +} + +func (_i *advertisementService) Update(id uint, req request.AdvertisementUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + return _i.Repo.Update(id, newReq) +} + +func (_i *advertisementService) UpdatePublish(id uint, isPublish bool) (err error) { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish"). + Interface("ids", id).Msg("") + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish"). + Interface("isPublish", isPublish).Msg("") + + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + result.IsPublish = isPublish + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UpdatePublish"). + Interface("result", result).Msg("") + + return _i.Repo.Update(id, result) +} + +func (_i *advertisementService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + result.IsActive = false + return _i.Repo.Update(id, result) +} + +func (_i *advertisementService) Viewer(c *fiber.Ctx) (err error) { + filename := c.Params("filename") + result, err := _i.Repo.FindByFilename(filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.ContentFilePath + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + // Tentukan Content-Type berdasarkan ekstensi file + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} diff --git a/app/module/ai_chat/ai_chat.module.go b/app/module/ai_chat/ai_chat.module.go new file mode 100644 index 0000000..ab774de --- /dev/null +++ b/app/module/ai_chat/ai_chat.module.go @@ -0,0 +1,65 @@ +package ai_chat + +import ( + "campaign-pool-be/app/module/ai_chat/controller" + "campaign-pool-be/app/module/ai_chat/repository" + "campaign-pool-be/app/module/ai_chat/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of AIChatRouter +type AIChatRouter struct { + App fiber.Router + Controller controller.AIChatController +} + +// register bulky of AI Chat module +var NewAIChatModule = fx.Options( + // register repository of AI Chat module + fx.Provide(repository.NewAIChatRepository), + + // register service of AI Chat module + fx.Provide(service.NewAIChatService), + + // register controller of AI Chat module + fx.Provide(controller.NewAIChatController), + + // register router of AI Chat module + fx.Provide(NewAIChatRouter), +) + +// init AIChatRouter +func NewAIChatRouter(fiber *fiber.App, controller controller.AIChatController) *AIChatRouter { + return &AIChatRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of AI Chat module +func (_i *AIChatRouter) RegisterAIChatRoutes() { + // define controllers + aiChatController := _i.Controller + + // define routes + _i.App.Route("/ai-chat", func(router fiber.Router) { + // Sessions routes + router.Get("/sessions", aiChatController.GetUserSessions) + router.Get("/sessions/:id", aiChatController.GetSession) + router.Post("/sessions", aiChatController.CreateSession) + router.Put("/sessions/:id", aiChatController.UpdateSession) + router.Delete("/sessions/:id", aiChatController.DeleteSession) + + // Messages routes + router.Get("/sessions/:id/messages", aiChatController.GetSessionMessages) + router.Post("/sessions/messages", aiChatController.SendMessage) + router.Put("/sessions/messages/:messageId", aiChatController.UpdateMessage) + router.Delete("/sessions/messages/:messageId", aiChatController.DeleteMessage) + + // Logs routes + router.Get("/logs", aiChatController.GetUserLogs) + router.Get("/logs/:id", aiChatController.GetLog) + }) +} diff --git a/app/module/ai_chat/controller/ai_chat.controller.go b/app/module/ai_chat/controller/ai_chat.controller.go new file mode 100644 index 0000000..ada67a7 --- /dev/null +++ b/app/module/ai_chat/controller/ai_chat.controller.go @@ -0,0 +1,438 @@ +package controller + +import ( + "jaecoo-be/app/module/ai_chat/request" + "jaecoo-be/app/module/ai_chat/service" + "jaecoo-be/utils/paginator" + utilRes "jaecoo-be/utils/response" + utilVal "jaecoo-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type aiChatController struct { + aiChatService service.AIChatService + Log zerolog.Logger +} + +type AIChatController interface { + // AI Chat Sessions + GetUserSessions(c *fiber.Ctx) error + GetSession(c *fiber.Ctx) error + CreateSession(c *fiber.Ctx) error + UpdateSession(c *fiber.Ctx) error + DeleteSession(c *fiber.Ctx) error + + // AI Chat Messages + GetSessionMessages(c *fiber.Ctx) error + SendMessage(c *fiber.Ctx) error + UpdateMessage(c *fiber.Ctx) error + DeleteMessage(c *fiber.Ctx) error + + // AI Chat Logs + GetUserLogs(c *fiber.Ctx) error + GetLog(c *fiber.Ctx) error +} + +func NewAIChatController(aiChatService service.AIChatService, log zerolog.Logger) AIChatController { + return &aiChatController{ + aiChatService: aiChatService, + Log: log, + } +} + +// Get User Sessions +// @Summary Get user AI chat sessions +// @Description API for getting all AI chat sessions for authenticated user +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.AIChatSessionsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions [get] +func (_i *aiChatController) GetUserSessions(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + reqContext := request.AIChatSessionsQueryRequestContext{ + IsActive: c.Query("isActive"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + sessionsData, paging, err := _i.aiChatService.GetUserSessions(authHeader, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat sessions successfully retrieved"}, + Data: sessionsData, + Meta: paging, + }) +} + +// Get Session +// @Summary Get one AI chat session +// @Description API for getting one AI chat session +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Session ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/{id} [get] +func (_i *aiChatController) GetSession(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + sessionData, err := _i.aiChatService.GetSession(authHeader, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat session successfully retrieved"}, + Data: sessionData, + }) +} + +// Create Session +// @Summary Create AI chat session +// @Description API for create AI chat session +// @Tags AI Chat +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.AIChatSessionsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions [post] +func (_i *aiChatController) CreateSession(c *fiber.Ctx) error { + req := new(request.AIChatSessionsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authHeader := c.Get("Authorization") + + dataResult, err := _i.aiChatService.CreateSession(authHeader, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat session successfully created"}, + Data: dataResult, + }) +} + +// Update Session +// @Summary Update AI chat session +// @Description API for update AI chat session +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param id path int true "Session ID" +// @Param payload body request.AIChatSessionsUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/{id} [put] +func (_i *aiChatController) UpdateSession(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.AIChatSessionsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authHeader := c.Get("Authorization") + + err = _i.aiChatService.UpdateSession(authHeader, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat session successfully updated"}, + }) +} + +// Delete Session +// @Summary Delete AI chat session +// @Description API for delete AI chat session +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Session ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/{id} [delete] +func (_i *aiChatController) DeleteSession(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + err = _i.aiChatService.DeleteSession(authHeader, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat session successfully deleted"}, + }) +} + +// Get Session Messages +// @Summary Get AI chat session messages +// @Description API for getting all messages in an AI chat session +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Session ID" +// @Param req query request.AIChatMessagesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/{id}/messages [get] +func (_i *aiChatController) GetSessionMessages(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + reqContext := request.AIChatMessagesQueryRequestContext{} + req := reqContext.ToParamRequest() + req.Pagination = paginate + + messagesData, paging, err := _i.aiChatService.GetSessionMessages(authHeader, uint(id), req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat messages successfully retrieved"}, + Data: messagesData, + Meta: paging, + }) +} + +// Send Message +// @Summary Send message to AI chat session +// @Description API for sending a message to an AI chat session +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.AIChatMessagesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/messages [post] +func (_i *aiChatController) SendMessage(c *fiber.Ctx) error { + req := new(request.AIChatMessagesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authHeader := c.Get("Authorization") + + dataResult, err := _i.aiChatService.SendMessage(authHeader, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Message successfully sent"}, + Data: dataResult, + }) +} + +// Update Message +// @Summary Update AI chat message +// @Description API for update AI chat message +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param messageId path int true "Message ID" +// @Param payload body request.AIChatMessagesUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/messages/{messageId} [put] +func (_i *aiChatController) UpdateMessage(c *fiber.Ctx) error { + messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0) + if err != nil { + return err + } + + req := new(request.AIChatMessagesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authHeader := c.Get("Authorization") + + err = _i.aiChatService.UpdateMessage(authHeader, uint(messageId), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat message successfully updated"}, + }) +} + +// Delete Message +// @Summary Delete AI chat message +// @Description API for delete AI chat message +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param messageId path int true "Message ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/sessions/messages/{messageId} [delete] +func (_i *aiChatController) DeleteMessage(c *fiber.Ctx) error { + messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + err = _i.aiChatService.DeleteMessage(authHeader, uint(messageId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat message successfully deleted"}, + }) +} + +// Get User Logs +// @Summary Get user AI chat logs +// @Description API for getting all AI chat logs for authenticated user +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.AIChatLogsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ai-chat/logs [get] +func (_i *aiChatController) GetUserLogs(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + reqContext := request.AIChatLogsQueryRequestContext{ + LogType: c.Query("logType"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + logsData, paging, err := _i.aiChatService.GetUserLogs(authHeader, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"AI chat logs successfully retrieved"}, + Data: logsData, + Meta: paging, + }) +} + +// Get Log +// @Summary Get one AI chat log +// @Description API for getting one AI chat log +// @Tags AI Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @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, + }) +} diff --git a/app/module/ai_chat/mapper/ai_chat.mapper.go b/app/module/ai_chat/mapper/ai_chat.mapper.go new file mode 100644 index 0000000..59f7a8b --- /dev/null +++ b/app/module/ai_chat/mapper/ai_chat.mapper.go @@ -0,0 +1,71 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ai_chat/response" +) + +func AIChatSessionsResponseMapper(session *entity.AIChatSessions) *response.AIChatSessionsResponse { + result := &response.AIChatSessionsResponse{ + ID: session.ID, + SessionID: session.SessionID, + UserID: session.UserID, + AgentID: session.AgentID, + Title: session.Title, + MessageCount: session.MessageCount, + IsActive: session.IsActive, + CreatedAt: session.CreatedAt, + UpdatedAt: session.UpdatedAt, + } + + if session.User != nil { + result.User = &response.UserBasicInfo{ + ID: session.User.ID, + Username: session.User.Username, + Fullname: session.User.Fullname, + Email: session.User.Email, + } + } + + return result +} + +func AIChatMessagesResponseMapper(message *entity.AIChatMessages) *response.AIChatMessagesResponse { + return &response.AIChatMessagesResponse{ + ID: message.ID, + SessionID: message.SessionID, + MessageType: message.MessageType, + Content: message.Content, + IsActive: message.IsActive, + CreatedAt: message.CreatedAt, + } +} + +func AIChatSessionWithMessagesResponseMapper(session *entity.AIChatSessions, messages []*entity.AIChatMessages) *response.AIChatSessionWithMessagesResponse { + sessionResponse := AIChatSessionsResponseMapper(session) + + var messagesResponse []*response.AIChatMessagesResponse + for _, message := range messages { + messagesResponse = append(messagesResponse, AIChatMessagesResponseMapper(message)) + } + + return &response.AIChatSessionWithMessagesResponse{ + Session: sessionResponse, + Messages: messagesResponse, + } +} + +func AIChatLogsResponseMapper(log *entity.AIChatLogs) *response.AIChatLogsResponse { + result := &response.AIChatLogsResponse{ + ID: log.ID, + UserID: log.UserID, + SessionID: log.SessionID, + StartDate: log.StartDate, + EndDate: log.EndDate, + TotalDuration: log.TotalDuration, + CreatedAt: log.CreatedAt, + UpdatedAt: log.UpdatedAt, + } + + return result +} diff --git a/app/module/ai_chat/repository/ai_chat.repository.go b/app/module/ai_chat/repository/ai_chat.repository.go new file mode 100644 index 0000000..78a72d1 --- /dev/null +++ b/app/module/ai_chat/repository/ai_chat.repository.go @@ -0,0 +1,198 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ai_chat/request" + "campaign-pool-be/utils/paginator" +) + +type aiChatRepository struct { + DB *database.Database +} + +type AIChatRepository interface { + // AI Chat Sessions + GetUserSessions(userId uint, req request.AIChatSessionsQueryRequest) (sessions []*entity.AIChatSessions, paging paginator.Pagination, err error) + FindSessionByUserAndId(userId uint, sessionId uint) (session *entity.AIChatSessions, err error) + FindSessionBySessionId(sessionId string) (session *entity.AIChatSessions, err error) + CreateSession(session *entity.AIChatSessions) (result *entity.AIChatSessions, err error) + UpdateSession(userId uint, sessionId uint, session *entity.AIChatSessions) (err error) + DeleteSession(userId uint, sessionId uint) (err error) + IncrementMessageCount(sessionId uint) (err error) + + // AI Chat Messages + GetSessionMessages(sessionId string, req request.AIChatMessagesQueryRequest) (messages []*entity.AIChatMessages, paging paginator.Pagination, err error) + CreateMessage(message *entity.AIChatMessages) (result *entity.AIChatMessages, err error) + UpdateMessage(messageId uint, message *entity.AIChatMessages) (err error) + DeleteMessage(messageId uint) (err error) + GetLastMessage(sessionId string) (message *entity.AIChatMessages, err error) + + // AI Chat Logs + GetUserLogs(userId uint, req request.AIChatLogsQueryRequest) (logs []*entity.AIChatLogs, paging paginator.Pagination, err error) + FindLogByUserAndId(userId uint, logId uint) (log *entity.AIChatLogs, err error) +} + +func NewAIChatRepository(db *database.Database) AIChatRepository { + return &aiChatRepository{ + DB: db, + } +} + +// AI Chat Sessions methods +func (_i *aiChatRepository) GetUserSessions(userId uint, req request.AIChatSessionsQueryRequest) (sessions []*entity.AIChatSessions, paging paginator.Pagination, err error) { + query := _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ?", userId) + + // Apply filters + if req.IsActive != nil { + query = query.Where("is_active = ?", *req.IsActive) + } + + // Include user relationship + // query = query.Preload("User") + + // Order by updated_at desc (most recent first) + query = query.Order("updated_at DESC") + + // Apply pagination + var count int64 + query.Count(&count) + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&sessions).Error + paging = *req.Pagination + + return +} + +func (_i *aiChatRepository) FindSessionByUserAndId(userId uint, sessionId uint) (session *entity.AIChatSessions, err error) { + err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ? AND id = ?", userId, sessionId).Preload("User").First(&session).Error + return +} + +func (_i *aiChatRepository) FindSessionBySessionId(sessionId string) (session *entity.AIChatSessions, err error) { + err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("session_id = ?", sessionId).Preload("User").First(&session).Error + return +} + +func (_i *aiChatRepository) CreateSession(session *entity.AIChatSessions) (result *entity.AIChatSessions, err error) { + err = _i.DB.DB.Create(session).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Model(&entity.AIChatSessions{}).Preload("User").First(&result, session.ID).Error + return +} + +func (_i *aiChatRepository) UpdateSession(userId uint, sessionId uint, session *entity.AIChatSessions) (err error) { + err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ? AND id = ?", userId, sessionId).Updates(session).Error + return +} + +func (_i *aiChatRepository) DeleteSession(userId uint, sessionId uint) (err error) { + err = _i.DB.DB.Model(&entity.AIChatSessions{}).Where("user_id = ? AND id = ?", userId, sessionId).Delete(&entity.AIChatSessions{}).Error + return +} + +func (_i *aiChatRepository) IncrementMessageCount(sessionId uint) (err error) { + err = _i.DB.DB.Exec("UPDATE ai_chat_sessions SET message_count = message_count + 1 WHERE id = ?", sessionId).Error + return +} + +// AI Chat Messages methods +func (_i *aiChatRepository) GetSessionMessages(sessionId string, req request.AIChatMessagesQueryRequest) (messages []*entity.AIChatMessages, paging paginator.Pagination, err error) { + query := _i.DB.DB.Model(&entity.AIChatMessages{}).Where("session_id = ?", sessionId) + + // Order by created_at asc (oldest first for chat) + query = query.Order("created_at ASC") + + // Apply pagination + var count int64 + query.Count(&count) + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&messages).Error + paging = *req.Pagination + + return +} + +func (_i *aiChatRepository) CreateMessage(message *entity.AIChatMessages) (result *entity.AIChatMessages, err error) { + err = _i.DB.DB.Create(message).Error + if err != nil { + return nil, err + } + + // Reload + err = _i.DB.DB.Model(&entity.AIChatMessages{}).First(&result, message.ID).Error + return +} + +func (_i *aiChatRepository) UpdateMessage(messageId uint, message *entity.AIChatMessages) (err error) { + err = _i.DB.DB.Model(&entity.AIChatMessages{}).Where("id = ?", messageId).Updates(message).Error + return +} + +func (_i *aiChatRepository) DeleteMessage(messageId uint) (err error) { + err = _i.DB.DB.Model(&entity.AIChatMessages{}).Where("id = ?", messageId).Delete(&entity.AIChatMessages{}).Error + return +} + +// AI Chat Logs methods +func (_i *aiChatRepository) GetUserLogs(userId uint, req request.AIChatLogsQueryRequest) (logs []*entity.AIChatLogs, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.AIChatLogs{}).Where("user_id = ?", userId) + + // Note: AIChatLogs entity doesn't have LogType field, so we skip this filter + // if req.LogType != nil && *req.LogType != "" { + // query = query.Where("log_type = ?", *req.LogType) + // } + + // Count total records + err = query.Count(&count).Error + if err != nil { + return + } + + // Apply pagination + if req.Pagination != nil { + query = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit) + } + + // Order by created_at desc (newest first) + query = query.Order("created_at DESC") + + err = query.Find(&logs).Error + if err != nil { + return + } + + // Set pagination info + if req.Pagination != nil { + paging = *req.Pagination + paging.Count = count + paging.TotalPage = int((count + int64(req.Pagination.Limit) - 1) / int64(req.Pagination.Limit)) + } + + return +} + +func (_i *aiChatRepository) FindLogByUserAndId(userId uint, logId uint) (log *entity.AIChatLogs, err error) { + query := _i.DB.DB.Model(&entity.AIChatLogs{}).Where("user_id = ? AND id = ?", userId, logId) + + if err := query.First(&log).Error; err != nil { + return nil, err + } + + return +} + +func (_i *aiChatRepository) GetLastMessage(sessionId string) (message *entity.AIChatMessages, err error) { + err = _i.DB.DB.Model(&entity.AIChatMessages{}).Where("session_id = ?", sessionId).Order("created_at DESC").First(&message).Error + return +} diff --git a/app/module/ai_chat/request/ai_chat.request.go b/app/module/ai_chat/request/ai_chat.request.go new file mode 100644 index 0000000..8ccdbfe --- /dev/null +++ b/app/module/ai_chat/request/ai_chat.request.go @@ -0,0 +1,114 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" +) + +// AI Chat Sessions Request DTOs +type AIChatSessionsQueryRequest struct { + IsActive *bool `json:"isActive"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type AIChatSessionsCreateRequest struct { + SessionID string `json:"sessionId" validate:"required"` + Title string `json:"title" validate:"required,min=2,max=255"` + AgentID string `json:"agentId" validate:"required"` +} + +func (req AIChatSessionsCreateRequest) ToEntity() *entity.AIChatSessions { + return &entity.AIChatSessions{ + SessionID: req.SessionID, + AgentID: req.AgentID, + Title: req.Title, + MessageCount: 0, + IsActive: true, + } +} + +type AIChatSessionsUpdateRequest struct { + Title string `json:"title" validate:"required,min=2,max=255"` + IsActive bool `json:"isActive"` +} + +func (req AIChatSessionsUpdateRequest) ToEntity() *entity.AIChatSessions { + return &entity.AIChatSessions{ + Title: req.Title, + IsActive: req.IsActive, + } +} + +// AI Chat Messages Request DTOs +type AIChatMessagesQueryRequest struct { + Pagination *paginator.Pagination `json:"pagination"` +} + +type AIChatMessagesCreateRequest struct { + SessionID string `json:"sessionId" validate:"required"` + MessageType string `json:"messageType" validate:"required,oneof=user assistant"` + Content string `json:"content" validate:"required,min=1"` +} + +func (req AIChatMessagesCreateRequest) ToEntity() *entity.AIChatMessages { + return &entity.AIChatMessages{ + SessionID: req.SessionID, + MessageType: req.MessageType, + Content: req.Content, + IsActive: true, + } +} + +type AIChatMessagesUpdateRequest struct { + Content string `json:"content" validate:"required,min=1"` +} + +func (req AIChatMessagesUpdateRequest) ToEntity() *entity.AIChatMessages { + return &entity.AIChatMessages{ + Content: req.Content, + } +} + +type AIChatSessionsQueryRequestContext struct { + IsActive string `json:"isActive"` +} + +func (req AIChatSessionsQueryRequestContext) ToParamRequest() AIChatSessionsQueryRequest { + var request AIChatSessionsQueryRequest + + if isActiveStr := req.IsActive; isActiveStr != "" { + isActive := isActiveStr == "true" + request.IsActive = &isActive + } + + return request +} + +type AIChatMessagesQueryRequestContext struct { +} + +func (req AIChatMessagesQueryRequestContext) ToParamRequest() AIChatMessagesQueryRequest { + var request AIChatMessagesQueryRequest + + return request +} + +// AI Chat Logs Request DTOs +type AIChatLogsQueryRequest struct { + LogType *string `json:"logType"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type AIChatLogsQueryRequestContext struct { + LogType string `json:"logType"` +} + +func (req AIChatLogsQueryRequestContext) ToParamRequest() AIChatLogsQueryRequest { + var request AIChatLogsQueryRequest + + if logType := req.LogType; logType != "" { + request.LogType = &logType + } + + return request +} diff --git a/app/module/ai_chat/response/ai_chat.response.go b/app/module/ai_chat/response/ai_chat.response.go new file mode 100644 index 0000000..6dbfd30 --- /dev/null +++ b/app/module/ai_chat/response/ai_chat.response.go @@ -0,0 +1,63 @@ +package response + +import ( + "time" +) + +// AI Chat Sessions Response DTOs +type AIChatSessionsResponse struct { + ID uint `json:"id"` + SessionID string `json:"sessionId"` + UserID uint `json:"userId"` + AgentID string `json:"agentId"` + Title string `json:"title"` + MessageCount int `json:"messageCount"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Nested user info + User *UserBasicInfo `json:"user,omitempty"` + + // Last message preview + LastMessage *AIChatMessagesResponse `json:"lastMessage,omitempty"` +} + +// AI Chat Messages Response DTOs +type AIChatMessagesResponse struct { + ID uint `json:"id"` + SessionID string `json:"sessionId"` + MessageType string `json:"messageType"` + Content string `json:"content"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` +} + +type UserBasicInfo struct { + ID uint `json:"id"` + Username string `json:"username"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} + +// Session with messages +type AIChatSessionWithMessagesResponse struct { + Session *AIChatSessionsResponse `json:"session"` + Messages []*AIChatMessagesResponse `json:"messages"` + Pagination interface{} `json:"pagination"` +} + +// AI Chat Logs Response DTOs +type AIChatLogsResponse struct { + ID uint `json:"id"` + UserID uint `json:"userId"` + SessionID uint `json:"sessionId"` + StartDate time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + TotalDuration int64 `json:"totalDuration"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Nested user info + User *UserBasicInfo `json:"user,omitempty"` +} diff --git a/app/module/ai_chat/service/ai_chat.service.go b/app/module/ai_chat/service/ai_chat.service.go new file mode 100644 index 0000000..fbcafa8 --- /dev/null +++ b/app/module/ai_chat/service/ai_chat.service.go @@ -0,0 +1,229 @@ +package service + +import ( + "campaign-pool-be/app/module/ai_chat/mapper" + "campaign-pool-be/app/module/ai_chat/repository" + "campaign-pool-be/app/module/ai_chat/request" + "campaign-pool-be/app/module/ai_chat/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + + "github.com/rs/zerolog" +) + +type aiChatService struct { + Repo repository.AIChatRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type AIChatService interface { + // Sessions + GetUserSessions(authToken string, req request.AIChatSessionsQueryRequest) (sessions []*response.AIChatSessionsResponse, paging paginator.Pagination, err error) + GetSession(authToken string, id uint) (session *response.AIChatSessionsResponse, err error) + CreateSession(authToken string, req request.AIChatSessionsCreateRequest) (session *response.AIChatSessionsResponse, err error) + UpdateSession(authToken string, id uint, req request.AIChatSessionsUpdateRequest) (err error) + DeleteSession(authToken string, id uint) error + ArchiveSession(authToken string, id uint) error + + // Messages + GetSessionMessages(authToken string, sessionId uint, req request.AIChatMessagesQueryRequest) (messages []*response.AIChatMessagesResponse, paging paginator.Pagination, err error) + SendMessage(authToken string, req request.AIChatMessagesCreateRequest) (message *response.AIChatMessagesResponse, err error) + UpdateMessage(authToken string, messageId uint, req request.AIChatMessagesUpdateRequest) (err error) + DeleteMessage(authToken string, messageId uint) error + + // Logs + GetUserLogs(authToken string, req request.AIChatLogsQueryRequest) (logs []*response.AIChatLogsResponse, paging paginator.Pagination, err error) + GetLog(authToken string, logId uint) (log *response.AIChatLogsResponse, err error) +} + +func NewAIChatService(repo repository.AIChatRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) AIChatService { + return &aiChatService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + } +} + +// Sessions methods +func (_i *aiChatService) GetUserSessions(authToken string, req request.AIChatSessionsQueryRequest) (sessions []*response.AIChatSessionsResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + results, paging, err := _i.Repo.GetUserSessions(userInfo.ID, req) + if err != nil { + return + } + + for _, result := range results { + sessions = append(sessions, mapper.AIChatSessionsResponseMapper(result)) + } + + return +} + +func (_i *aiChatService) GetSession(authToken string, id uint) (session *response.AIChatSessionsResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + result, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id) + if err != nil { + return nil, err + } + + return mapper.AIChatSessionsResponseMapper(result), nil +} + +func (_i *aiChatService) CreateSession(authToken string, req request.AIChatSessionsCreateRequest) (session *response.AIChatSessionsResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + entity := req.ToEntity() + entity.UserID = userInfo.ID + + _i.Log.Info().Interface("data", entity).Msg("Create AI chat session") + + result, err := _i.Repo.CreateSession(entity) + if err != nil { + return nil, err + } + + return mapper.AIChatSessionsResponseMapper(result), nil +} + +func (_i *aiChatService) UpdateSession(authToken string, id uint, req request.AIChatSessionsUpdateRequest) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + _i.Log.Info().Interface("data", req).Msg("Updating AI chat session") + + // Check if session exists and belongs to user + existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("AI chat session not found") + } + + entity := req.ToEntity() + return _i.Repo.UpdateSession(userInfo.ID, id, entity) +} + +func (_i *aiChatService) DeleteSession(authToken string, id uint) error { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + // Check if session exists and belongs to user + existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("AI chat session not found") + } + + return _i.Repo.DeleteSession(userInfo.ID, id) +} + +func (_i *aiChatService) ArchiveSession(authToken string, id uint) error { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + // Check if session exists and belongs to user + existing, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("AI chat session not found") + } + + // Update status to archived + existing.IsActive = false + return _i.Repo.UpdateSession(userInfo.ID, id, existing) +} + +// Messages methods +func (_i *aiChatService) GetSessionMessages(authToken string, sessionId uint, req request.AIChatMessagesQueryRequest) (messages []*response.AIChatMessagesResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + // Verify session belongs to user + session, err := _i.Repo.FindSessionByUserAndId(userInfo.ID, sessionId) + if err != nil { + return nil, paginator.Pagination{}, err + } + + results, paging, err := _i.Repo.GetSessionMessages(session.SessionID, req) + if err != nil { + return + } + + for _, result := range results { + messages = append(messages, mapper.AIChatMessagesResponseMapper(result)) + } + + return +} + +func (_i *aiChatService) SendMessage(authToken string, req request.AIChatMessagesCreateRequest) (message *response.AIChatMessagesResponse, err error) { + // userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + // Verify session exists + session, err := _i.Repo.FindSessionBySessionId(req.SessionID) + if err != nil { + return nil, err + } + + entity := req.ToEntity() + + result, err := _i.Repo.CreateMessage(entity) + if err != nil { + return nil, err + } + + // Increment message count in session + err = _i.Repo.IncrementMessageCount(session.ID) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to increment message count") + } + + return mapper.AIChatMessagesResponseMapper(result), nil +} + +// Update Message +func (_i *aiChatService) UpdateMessage(authToken string, messageId uint, req request.AIChatMessagesUpdateRequest) (err error) { + // userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + entity := req.ToEntity() + return _i.Repo.UpdateMessage(messageId, entity) +} + +// Delete Message +func (_i *aiChatService) DeleteMessage(authToken string, messageId uint) error { + // userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + return _i.Repo.DeleteMessage(messageId) +} + +// Logs methods +func (_i *aiChatService) GetUserLogs(authToken string, req request.AIChatLogsQueryRequest) (logs []*response.AIChatLogsResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + results, paging, err := _i.Repo.GetUserLogs(userInfo.ID, req) + if err != nil { + return + } + + for _, result := range results { + logs = append(logs, mapper.AIChatLogsResponseMapper(result)) + } + + return +} + +func (_i *aiChatService) GetLog(authToken string, logId uint) (log *response.AIChatLogsResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + result, err := _i.Repo.FindLogByUserAndId(userInfo.ID, logId) + if err != nil { + return nil, err + } + + return mapper.AIChatLogsResponseMapper(result), nil +} diff --git a/app/module/article_approvals/article_approvals.module.go b/app/module/article_approvals/article_approvals.module.go new file mode 100644 index 0000000..0469554 --- /dev/null +++ b/app/module/article_approvals/article_approvals.module.go @@ -0,0 +1,54 @@ +package article_approvals + +import ( + "campaign-pool-be/app/module/article_approvals/controller" + "campaign-pool-be/app/module/article_approvals/repository" + "campaign-pool-be/app/module/article_approvals/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ArticleApprovalsRouter +type ArticleApprovalsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleApprovals module +var NewArticleApprovalsModule = fx.Options( + // register repository of ArticleApprovals module + fx.Provide(repository.NewArticleApprovalsRepository), + + // register service of ArticleApprovals module + fx.Provide(service.NewArticleApprovalsService), + + // register controller of ArticleApprovals module + fx.Provide(controller.NewController), + + // register router of ArticleApprovals module + fx.Provide(NewArticleApprovalsRouter), +) + +// init ArticleApprovalsRouter +func NewArticleApprovalsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleApprovalsRouter { + return &ArticleApprovalsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleApprovals module +func (_i *ArticleApprovalsRouter) RegisterArticleApprovalsRoutes() { + // define controllers + articleApprovalsController := _i.Controller.ArticleApprovals + + // define routes + _i.App.Route("/article-approvals", func(router fiber.Router) { + router.Get("/", articleApprovalsController.All) + router.Get("/:id", articleApprovalsController.Show) + router.Post("/", articleApprovalsController.Save) + router.Put("/:id", articleApprovalsController.Update) + router.Delete("/:id", articleApprovalsController.Delete) + }) +} diff --git a/app/module/article_approvals/controller/article_approvals.controller.go b/app/module/article_approvals/controller/article_approvals.controller.go new file mode 100644 index 0000000..eb9c9ee --- /dev/null +++ b/app/module/article_approvals/controller/article_approvals.controller.go @@ -0,0 +1,202 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_approvals/request" + "campaign-pool-be/app/module/article_approvals/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type articleApprovalsController struct { + articleApprovalsService service.ArticleApprovalsService + Log zerolog.Logger +} + +type ArticleApprovalsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewArticleApprovalsController(articleApprovalsService service.ArticleApprovalsService, log zerolog.Logger) ArticleApprovalsController { + return &articleApprovalsController{ + articleApprovalsService: articleApprovalsService, + Log: log, + } +} + +// All get all ArticleApprovals +// @Summary Get all ArticleApprovals +// @Description API for getting all ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param req query request.ArticleApprovalsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals [get] +func (_i *articleApprovalsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleApprovalsQueryRequestContext{ + ArticleId: c.Query("articleId"), + ApprovalBy: c.Query("approvalBy"), + StatusId: c.Query("statusId"), + Message: c.Query("message"), + ApprovalAtLevel: c.Query("approvalAtLevel"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleApprovalsData, paging, err := _i.articleApprovalsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals list successfully retrieved"}, + Data: articleApprovalsData, + Meta: paging, + }) +} + +// Show get one ArticleApprovals +// @Summary Get one ArticleApprovals +// @Description API for getting one ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param id path int true "ArticleApprovals ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-approvals/{id} [get] +func (_i *articleApprovalsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleApprovalsData, err := _i.articleApprovalsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleApprovals successfully retrieved"}, + Data: articleApprovalsData, + }) +} + +// Save create ArticleApprovals +// @Summary Create ArticleApprovals +// @Description API for create ArticleApprovals +// @Tags ArticleApprovals +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @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"}, + }) +} diff --git a/app/module/article_approvals/controller/controller.go b/app/module/article_approvals/controller/controller.go new file mode 100644 index 0000000..e698dfc --- /dev/null +++ b/app/module/article_approvals/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_approvals/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + ArticleApprovals ArticleApprovalsController +} + +func NewController(ArticleApprovalsService service.ArticleApprovalsService, log zerolog.Logger) *Controller { + return &Controller{ + ArticleApprovals: NewArticleApprovalsController(ArticleApprovalsService, log), + } +} diff --git a/app/module/article_approvals/mapper/article_approvals.mapper.go b/app/module/article_approvals/mapper/article_approvals.mapper.go new file mode 100644 index 0000000..e21495e --- /dev/null +++ b/app/module/article_approvals/mapper/article_approvals.mapper.go @@ -0,0 +1,21 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/article_approvals/response" +) + +func ArticleApprovalsResponseMapper(articleApprovalsReq *entity.ArticleApprovals) (articleApprovalsRes *res.ArticleApprovalsResponse) { + if articleApprovalsReq != nil { + articleApprovalsRes = &res.ArticleApprovalsResponse{ + ID: articleApprovalsReq.ID, + ArticleId: articleApprovalsReq.ArticleId, + ApprovalBy: articleApprovalsReq.ApprovalBy, + StatusId: articleApprovalsReq.StatusId, + Message: articleApprovalsReq.Message, + ApprovalAtLevel: articleApprovalsReq.ApprovalAtLevel, + CreatedAt: articleApprovalsReq.CreatedAt, + } + } + return articleApprovalsRes +} diff --git a/app/module/article_approvals/repository/article_approvals.repository.go b/app/module/article_approvals/repository/article_approvals.repository.go new file mode 100644 index 0000000..0662e99 --- /dev/null +++ b/app/module/article_approvals/repository/article_approvals.repository.go @@ -0,0 +1,105 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_approvals/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type articleApprovalsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleApprovalsRepository define interface of IArticleApprovalsRepository +type ArticleApprovalsRepository interface { + GetAll(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*entity.ArticleApprovals, paging paginator.Pagination, err error) + FindOne(id uint) (articleApprovals *entity.ArticleApprovals, err error) + Create(articleApprovals *entity.ArticleApprovals) (articleApprovalsReturn *entity.ArticleApprovals, err error) + Update(id uint, articleApprovals *entity.ArticleApprovals) (err error) + Delete(id uint) (err error) +} + +func NewArticleApprovalsRepository(db *database.Database, logger zerolog.Logger) ArticleApprovalsRepository { + return &articleApprovalsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IArticleApprovalsRepository +func (_i *articleApprovalsRepository) GetAll(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*entity.ArticleApprovals, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleApprovals{}) + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.ApprovalBy != nil { + query = query.Where("approval_by = ?", req.ApprovalBy) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.ApprovalAtLevel != nil { + query = query.Where("approval_at_level = ?", req.ApprovalAtLevel) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleApprovalss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleApprovalsRepository) FindOne(id uint) (articleApprovals *entity.ArticleApprovals, err error) { + if err := _i.DB.DB.First(&articleApprovals, id).Error; err != nil { + return nil, err + } + + return articleApprovals, nil +} + +func (_i *articleApprovalsRepository) Create(articleApprovals *entity.ArticleApprovals) (articleApprovalsReturn *entity.ArticleApprovals, err error) { + result := _i.DB.DB.Create(articleApprovals) + return articleApprovals, result.Error +} + +func (_i *articleApprovalsRepository) Update(id uint, articleApprovals *entity.ArticleApprovals) (err error) { + articleApprovalsMap, err := utilSvc.StructToMap(articleApprovals) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.ArticleApprovals{}). + Where(&entity.ArticleApprovals{ID: id}). + Updates(articleApprovalsMap).Error +} + +func (_i *articleApprovalsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.ArticleApprovals{}, id).Error +} diff --git a/app/module/article_approvals/request/article_approvals.request.go b/app/module/article_approvals/request/article_approvals.request.go new file mode 100644 index 0000000..897bc4b --- /dev/null +++ b/app/module/article_approvals/request/article_approvals.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" +) + +type ArticleApprovalsGeneric interface { + ToEntity() +} + +type ArticleApprovalsQueryRequest struct { + ArticleId *int `json:"articleId"` + ApprovalBy *int `json:"approvalBy"` + StatusId *int `json:"statusId"` + Message *string `json:"message"` + ApprovalAtLevel *int `json:"approvalAtLevel"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleApprovalsCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Message string `json:"message" validate:"required"` +} + +func (req ArticleApprovalsCreateRequest) ToEntity() *entity.ArticleApprovals { + return &entity.ArticleApprovals{ + ArticleId: req.ArticleId, + StatusId: req.StatusId, + Message: req.Message, + } +} + +type ArticleApprovalsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Message string `json:"message" validate:"required"` +} + +func (req ArticleApprovalsUpdateRequest) ToEntity() *entity.ArticleApprovals { + return &entity.ArticleApprovals{ + ID: req.ID, + ArticleId: req.ArticleId, + StatusId: req.StatusId, + Message: req.Message, + } +} + +type ArticleApprovalsQueryRequestContext struct { + ArticleId string `json:"articleId"` + ApprovalBy string `json:"approvalBy"` + StatusId string `json:"statusId"` + Message string `json:"message"` + ApprovalAtLevel string `json:"approvalAtLevel"` +} + +func (req ArticleApprovalsQueryRequestContext) ToParamRequest() ArticleApprovalsQueryRequest { + var request ArticleApprovalsQueryRequest + + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if approvalByStr := req.ApprovalBy; approvalByStr != "" { + approvalBy, err := strconv.Atoi(approvalByStr) + if err == nil { + request.ApprovalBy = &approvalBy + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if message := req.Message; message != "" { + request.Message = &message + } + if approvalAtLevelStr := req.ApprovalAtLevel; approvalAtLevelStr != "" { + approvalAtLevel, err := strconv.Atoi(approvalAtLevelStr) + if err == nil { + request.ApprovalAtLevel = &approvalAtLevel + } + } + + return request +} diff --git a/app/module/article_approvals/response/article_approvals.response.go b/app/module/article_approvals/response/article_approvals.response.go new file mode 100644 index 0000000..4b56b0a --- /dev/null +++ b/app/module/article_approvals/response/article_approvals.response.go @@ -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"` +} diff --git a/app/module/article_approvals/service/article_approvals.service.go b/app/module/article_approvals/service/article_approvals.service.go new file mode 100644 index 0000000..e087a21 --- /dev/null +++ b/app/module/article_approvals/service/article_approvals.service.go @@ -0,0 +1,96 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_approvals/mapper" + "campaign-pool-be/app/module/article_approvals/repository" + "campaign-pool-be/app/module/article_approvals/request" + "campaign-pool-be/app/module/article_approvals/response" + articlesService "campaign-pool-be/app/module/articles/service" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" + + utilSvc "campaign-pool-be/utils/service" +) + +// ArticleApprovalsService +type articleApprovalsService struct { + Repo repository.ArticleApprovalsRepository + UsersRepo usersRepository.UsersRepository + ArticlesService articlesService.ArticlesService + Log zerolog.Logger +} + +// ArticleApprovalsService define interface of IArticleApprovalsService +type ArticleApprovalsService interface { + All(req request.ArticleApprovalsQueryRequest) (articleApprovals []*response.ArticleApprovalsResponse, paging paginator.Pagination, err error) + Show(id uint) (articleApprovals *response.ArticleApprovalsResponse, err error) + Save(req request.ArticleApprovalsCreateRequest, authToken string) (articleApprovals *entity.ArticleApprovals, err error) + Update(id uint, req request.ArticleApprovalsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewArticleApprovalsService init ArticleApprovalsService +func NewArticleApprovalsService(repo repository.ArticleApprovalsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository, articlesSvc articlesService.ArticlesService) ArticleApprovalsService { + + return &articleApprovalsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + ArticlesService: articlesSvc, + } +} + +// All implement interface of ArticleApprovalsService +func (_i *articleApprovalsService) All(req request.ArticleApprovalsQueryRequest) (articleApprovalss []*response.ArticleApprovalsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleApprovalss = append(articleApprovalss, mapper.ArticleApprovalsResponseMapper(result)) + } + + return +} + +func (_i *articleApprovalsService) Show(id uint) (articleApprovals *response.ArticleApprovalsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleApprovalsResponseMapper(result), nil +} + +func (_i *articleApprovalsService) Save(req request.ArticleApprovalsCreateRequest, authToken string) (articleApprovals *entity.ArticleApprovals, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.ApprovalBy = createdBy.ID + newReq.ApprovalAtLevel = &createdBy.UserLevel.LevelNumber + + approvalByUserLevelId := createdBy.UserLevelId + approvalParentLevelId := createdBy.UserLevel.ParentLevelId + + err = _i.ArticlesService.UpdateApproval(newReq.ArticleId, newReq.StatusId, int(approvalByUserLevelId), *newReq.ApprovalAtLevel, *approvalParentLevelId) + if err != nil { + return nil, err + } + + return _i.Repo.Create(newReq) +} + +func (_i *articleApprovalsService) Update(id uint, req request.ArticleApprovalsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleApprovalsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/article_categories/article_categories.module.go b/app/module/article_categories/article_categories.module.go new file mode 100644 index 0000000..001e2bc --- /dev/null +++ b/app/module/article_categories/article_categories.module.go @@ -0,0 +1,58 @@ +package article_categories + +import ( + "campaign-pool-be/app/module/article_categories/controller" + "campaign-pool-be/app/module/article_categories/repository" + "campaign-pool-be/app/module/article_categories/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ArticleCategoriesRouter +type ArticleCategoriesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleCategories module +var NewArticleCategoriesModule = fx.Options( + // register repository of ArticleCategories module + fx.Provide(repository.NewArticleCategoriesRepository), + + // register service of ArticleCategories module + fx.Provide(service.NewArticleCategoriesService), + + // register controller of ArticleCategories module + fx.Provide(controller.NewController), + + // register router of ArticleCategories module + fx.Provide(NewArticleCategoriesRouter), +) + +// init ArticleCategoriesRouter +func NewArticleCategoriesRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCategoriesRouter { + return &ArticleCategoriesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleCategories module +func (_i *ArticleCategoriesRouter) RegisterArticleCategoriesRoutes() { + // define controllers + articleCategoriesController := _i.Controller.ArticleCategories + + // define routes + _i.App.Route("/article-categories", func(router fiber.Router) { + router.Get("/", articleCategoriesController.All) + router.Get("/:id", articleCategoriesController.Show) + router.Get("/old/:id", articleCategoriesController.ShowByOldId) + router.Get("/slug/:slug", articleCategoriesController.ShowBySlug) + router.Post("/", articleCategoriesController.Save) + router.Put("/:id", articleCategoriesController.Update) + router.Post("/thumbnail/:id", articleCategoriesController.SaveThumbnail) + router.Get("/thumbnail/viewer/:id", articleCategoriesController.Viewer) + router.Delete("/:id", articleCategoriesController.Delete) + }) +} diff --git a/app/module/article_categories/controller/article_categories.controller.go b/app/module/article_categories/controller/article_categories.controller.go new file mode 100644 index 0000000..1c8d4da --- /dev/null +++ b/app/module/article_categories/controller/article_categories.controller.go @@ -0,0 +1,298 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_categories/request" + "campaign-pool-be/app/module/article_categories/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type articleCategoriesController struct { + articleCategoriesService service.ArticleCategoriesService +} + +type ArticleCategoriesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByOldId(c *fiber.Ctx) error + ShowBySlug(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewArticleCategoriesController(articleCategoriesService service.ArticleCategoriesService) ArticleCategoriesController { + return &articleCategoriesController{ + articleCategoriesService: articleCategoriesService, + } +} + +// All ArticleCategories +// @Summary Get all ArticleCategories +// @Description API for getting all ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.ArticleCategoriesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories [get] +func (_i *articleCategoriesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + reqContext := request.ArticleCategoriesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + ParentId: c.Query("parentId"), + IsPublish: c.Query("isPublish"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleCategoriesData, paging, err := _i.articleCategoriesService.All(req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories list successfully retrieved"}, + Data: articleCategoriesData, + Meta: paging, + }) +} + +// Show ArticleCategories +// @Summary Get one ArticleCategories +// @Description API for getting one ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/{id} [get] +func (_i *articleCategoriesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCategoriesData, err := _i.articleCategoriesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully retrieved"}, + Data: articleCategoriesData, + }) +} + +// ShowByOldId ArticleCategories +// @Summary Get one ArticleCategories +// @Description API for getting one ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param id path int true "ArticleCategories Old ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/old/{id} [get] +func (_i *articleCategoriesController) ShowByOldId(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCategoriesData, err := _i.articleCategoriesService.ShowByOldId(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully retrieved"}, + Data: articleCategoriesData, + }) +} + +// ShowBySlug ArticleCategories +// @Summary Get one ArticleCategories +// @Description API for getting one ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param slug path string true "ArticleCategories Slug" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/slug/{slug} [get] +func (_i *articleCategoriesController) ShowBySlug(c *fiber.Ctx) error { + slug := c.Params("slug") + articleCategoriesData, err := _i.articleCategoriesService.ShowBySlug(slug) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully retrieved"}, + Data: articleCategoriesData, + }) +} + +// Save ArticleCategories +// @Summary Create ArticleCategories +// @Description API for create ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticleCategoriesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories [post] +func (_i *articleCategoriesController) Save(c *fiber.Ctx) error { + req := new(request.ArticleCategoriesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.articleCategoriesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully created"}, + Data: dataResult, + }) +} + +// SaveThumbnail ArticleCategories +// @Summary Upload ArticleCategories Thumbnail +// @Description API for Upload ArticleCategories Thumbnail +// @Tags Article Categories +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param files formData file true "Upload thumbnail" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/thumbnail/{id} [post] +func (_i *articleCategoriesController) SaveThumbnail(c *fiber.Ctx) error { + err := _i.articleCategoriesService.SaveThumbnail(c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of ArticleCategories successfully created"}, + }) +} + +// Update ArticleCategories +// @Summary update ArticleCategories +// @Description API for update ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.ArticleCategoriesUpdateRequest true "Required payload" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/{id} [put] +func (_i *articleCategoriesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCategoriesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleCategoriesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully updated"}, + }) +} + +// Delete ArticleCategories +// @Summary delete ArticleCategories +// @Description API for delete ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/{id} [delete] +func (_i *articleCategoriesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleCategoriesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleCategories successfully deleted"}, + }) +} + +// Viewer ArticleCategories +// @Summary Viewer ArticleCategories +// @Description API for View Thumbnail of ArticleCategories +// @Tags Article Categories +// @Security Bearer +// @Param id path string true "ArticleCategories ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-categories/thumbnail/viewer/{id} [get] +func (_i *articleCategoriesController) Viewer(c *fiber.Ctx) error { + return _i.articleCategoriesService.Viewer(c) +} diff --git a/app/module/article_categories/controller/controller.go b/app/module/article_categories/controller/controller.go new file mode 100644 index 0000000..b0edb52 --- /dev/null +++ b/app/module/article_categories/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/article_categories/service" + +type Controller struct { + ArticleCategories ArticleCategoriesController +} + +func NewController(ArticleCategoriesService service.ArticleCategoriesService) *Controller { + return &Controller{ + ArticleCategories: NewArticleCategoriesController(ArticleCategoriesService), + } +} diff --git a/app/module/article_categories/mapper/article_categories.mapper.go b/app/module/article_categories/mapper/article_categories.mapper.go new file mode 100644 index 0000000..61a24d1 --- /dev/null +++ b/app/module/article_categories/mapper/article_categories.mapper.go @@ -0,0 +1,39 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/article_categories/response" + "strconv" + "strings" +) + +func ArticleCategoriesResponseMapper(articleCategoriesReq *entity.ArticleCategories, host string) (articleCategoriesRes *res.ArticleCategoriesResponse) { + if articleCategoriesReq != nil { + articleCategoriesRes = &res.ArticleCategoriesResponse{ + ID: articleCategoriesReq.ID, + Title: articleCategoriesReq.Title, + Description: articleCategoriesReq.Description, + Slug: articleCategoriesReq.Slug, + ThumbnailPath: articleCategoriesReq.ThumbnailPath, + ParentId: articleCategoriesReq.ParentId, + OldCategoryId: articleCategoriesReq.OldCategoryId, + CreatedById: articleCategoriesReq.CreatedById, + StatusId: articleCategoriesReq.StatusId, + IsPublish: articleCategoriesReq.IsPublish, + PublishedAt: articleCategoriesReq.PublishedAt, + IsActive: articleCategoriesReq.IsActive, + CreatedAt: articleCategoriesReq.CreatedAt, + UpdatedAt: articleCategoriesReq.UpdatedAt, + } + + if articleCategoriesReq.Tags != nil { + tagsValue := *articleCategoriesReq.Tags + articleCategoriesRes.Tags = strings.Split(tagsValue, ",") + } + + if articleCategoriesRes.ThumbnailPath != nil { + articleCategoriesRes.ThumbnailUrl = host + "/article-categories/thumbnail/viewer/" + strconv.Itoa(int(articleCategoriesReq.ID)) + } + } + return articleCategoriesRes +} diff --git a/app/module/article_categories/repository/article_categories.repository.go b/app/module/article_categories/repository/article_categories.repository.go new file mode 100644 index 0000000..c76aca0 --- /dev/null +++ b/app/module/article_categories/repository/article_categories.repository.go @@ -0,0 +1,141 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_categories/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type articleCategoriesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleCategoriesRepository define interface of IArticleCategoriesRepository +type ArticleCategoriesRepository interface { + GetAll(req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error) + FindOne(id uint) (articleCategories *entity.ArticleCategories, err error) + FindOneByOldId(id uint) (articleCategories *entity.ArticleCategories, err error) + FindOneBySlug(slug string) (articleCategories *entity.ArticleCategories, err error) + Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error) + Update(id uint, articleCategories *entity.ArticleCategories) (err error) + Delete(id uint) (err error) +} + +func NewArticleCategoriesRepository(db *database.Database, log zerolog.Logger) ArticleCategoriesRepository { + return &articleCategoriesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IArticleCategoriesRepository +func (_i *articleCategoriesRepository) GetAll(req request.ArticleCategoriesQueryRequest) (articleCategoriess []*entity.ArticleCategories, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleCategories{}) + + if req.UserLevelId != nil { + query = _i.DB.DB.Model(&entity.ArticleCategories{}). + Joins("LEFT JOIN users ON article_categories.created_by_id = users.id"). + Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id"). + Where("user_levels.id = ? or user_levels.parent_level_id = ?", *req.UserLevelId, *req.UserLevelId) + } + + query = query.Where("article_categories.is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(article_categories.title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(article_categories.description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.ParentId != nil { + query = query.Where("article_categories.parent_id = ?", req.ParentId) + } + if req.IsPublish != nil { + query = query.Where("article_categories.is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("article_categories.status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCategoriess).Error + + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCategoriesRepository) FindOne(id uint) (articleCategories *entity.ArticleCategories, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.First(&articleCategories).Error; err != nil { + return nil, err + } + + return articleCategories, nil +} + +func (_i *articleCategoriesRepository) FindOneByOldId(id uint) (articleCategories *entity.ArticleCategories, err error) { + query := _i.DB.DB.Where("old_category_id = ?", id) + + if err := query.First(&articleCategories).Error; err != nil { + return nil, err + } + + return articleCategories, nil +} + +func (_i *articleCategoriesRepository) FindOneBySlug(slug string) (articleCategories *entity.ArticleCategories, err error) { + query := _i.DB.DB.Where("slug = ?", slug) + + if err := query.First(&articleCategories).Error; err != nil { + return nil, err + } + + return articleCategories, nil +} + +func (_i *articleCategoriesRepository) Create(articleCategories *entity.ArticleCategories) (articleCategoriesReturn *entity.ArticleCategories, err error) { + result := _i.DB.DB.Create(articleCategories) + return articleCategories, result.Error +} + +func (_i *articleCategoriesRepository) Update(id uint, articleCategories *entity.ArticleCategories) (err error) { + articleCategoriesMap, err := utilSvc.StructToMap(articleCategories) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.ArticleCategories{}).Where(&entity.ArticleCategories{ID: id}) + return query.Updates(articleCategoriesMap).Error +} + +func (_i *articleCategoriesRepository) Delete(id uint) error { + query := _i.DB.DB.Model(&entity.ArticleCategories{}).Where("id = ?", id) + return query.Delete(&entity.ArticleCategories{}).Error +} diff --git a/app/module/article_categories/request/article_categories.request.go b/app/module/article_categories/request/article_categories.request.go new file mode 100644 index 0000000..1228f05 --- /dev/null +++ b/app/module/article_categories/request/article_categories.request.go @@ -0,0 +1,128 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type ArticleCategoriesGeneric interface { + ToEntity() +} + +type ArticleCategoriesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + UserLevelId *uint `json:"UserLevelId"` + UserLevelNumber *int `json:"UserLevelNumber"` + ParentId *int `json:"parentId"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCategoriesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Tags *string `json:"tags"` + Slug *string `json:"slug"` + CreatedById *uint `json:"createdById"` + ParentId *int `json:"parentId"` + OldCategoryId *uint `json:"oldCategoryId"` +} + +func (req ArticleCategoriesCreateRequest) ToEntity() *entity.ArticleCategories { + return &entity.ArticleCategories{ + Title: req.Title, + Description: req.Description, + Tags: req.Tags, + ParentId: req.ParentId, + Slug: req.Slug, + OldCategoryId: req.OldCategoryId, + StatusId: req.StatusId, + } +} + +type ArticleCategoriesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + Tags *string `json:"tags"` + Slug *string `json:"slug"` + ParentId *int `json:"parentId"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req ArticleCategoriesUpdateRequest) ToEntity() *entity.ArticleCategories { + return &entity.ArticleCategories{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + ParentId: req.ParentId, + Slug: req.Slug, + Tags: req.Tags, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + UpdatedAt: time.Now(), + } +} + +type ArticleCategoriesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + ParentId string `json:"parentId"` + StatusId string `json:"statusId"` + IsPublish string `json:"isPublish"` + UserLevelId string `json:"UserLevelId"` + UserLevelNumber string `json:"UserLevelNumber"` +} + +func (req ArticleCategoriesQueryRequestContext) ToParamRequest() ArticleCategoriesQueryRequest { + var request ArticleCategoriesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if parentIdStr := req.ParentId; parentIdStr != "" { + parentId, err := strconv.Atoi(parentIdStr) + if err == nil { + request.ParentId = &parentId + } + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if userLevelIdStr := req.UserLevelId; userLevelIdStr != "" { + userLevelId, err := strconv.Atoi(userLevelIdStr) + if err == nil { + userLevelIdUint := uint(userLevelId) + request.UserLevelId = &userLevelIdUint + } + } + if userLevelNumberStr := req.UserLevelNumber; userLevelNumberStr != "" { + userLevelNumber, err := strconv.Atoi(userLevelNumberStr) + if err == nil { + request.UserLevelNumber = &userLevelNumber + } + } + + return request +} diff --git a/app/module/article_categories/response/article_categories.response.go b/app/module/article_categories/response/article_categories.response.go new file mode 100644 index 0000000..27c012e --- /dev/null +++ b/app/module/article_categories/response/article_categories.response.go @@ -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"` +} diff --git a/app/module/article_categories/service/article_categories.service.go b/app/module/article_categories/service/article_categories.service.go new file mode 100644 index 0000000..5a4e485 --- /dev/null +++ b/app/module/article_categories/service/article_categories.service.go @@ -0,0 +1,281 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_categories/mapper" + "campaign-pool-be/app/module/article_categories/repository" + "campaign-pool-be/app/module/article_categories/request" + "campaign-pool-be/app/module/article_categories/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "context" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// ArticleCategoriesService +type articleCategoriesService struct { + Repo repository.ArticleCategoriesRepository + UsersRepo usersRepository.UsersRepository + MinioStorage *minioStorage.MinioStorage + Log zerolog.Logger + Cfg *config.Config +} + +// ArticleCategoriesService define interface of IArticleCategoriesService +type ArticleCategoriesService interface { + All(req request.ArticleCategoriesQueryRequest, authToken string) (articleCategories []*response.ArticleCategoriesResponse, paging paginator.Pagination, err error) + Show(id uint) (articleCategories *response.ArticleCategoriesResponse, err error) + ShowByOldId(id uint) (articleCategories *response.ArticleCategoriesResponse, err error) + ShowBySlug(slug string) (articleCategories *response.ArticleCategoriesResponse, err error) + Save(req request.ArticleCategoriesCreateRequest, authToken string) (articleCategories *entity.ArticleCategories, err error) + SaveThumbnail(c *fiber.Ctx) (err error) + Update(id uint, req request.ArticleCategoriesUpdateRequest) (err error) + Delete(id uint) error + Viewer(c *fiber.Ctx) error +} + +// NewArticleCategoriesService init ArticleCategoriesService +func NewArticleCategoriesService(repo repository.ArticleCategoriesRepository, usersRepo usersRepository.UsersRepository, minioStorage *minioStorage.MinioStorage, log zerolog.Logger, cfg *config.Config) ArticleCategoriesService { + + return &articleCategoriesService{ + Repo: repo, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of ArticleCategoriesService +func (_i *articleCategoriesService) All(req request.ArticleCategoriesQueryRequest, authToken string) (articleCategoriess []*response.ArticleCategoriesResponse, paging paginator.Pagination, err error) { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if createdBy != nil { + if createdBy.UserLevel.LevelNumber > 1 { + req.UserLevelId = &createdBy.UserLevelId + } + } + + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + for _, result := range results { + articleCategoriess = append(articleCategoriess, mapper.ArticleCategoriesResponseMapper(result, host)) + } + + return +} + +func (_i *articleCategoriesService) Show(id uint) (articleCategories *response.ArticleCategoriesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.ArticleCategoriesResponseMapper(result, host), nil +} + +func (_i *articleCategoriesService) ShowByOldId(id uint) (articleCategories *response.ArticleCategoriesResponse, err error) { + result, err := _i.Repo.FindOneByOldId(id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.ArticleCategoriesResponseMapper(result, host), nil +} + +func (_i *articleCategoriesService) ShowBySlug(slug string) (articleCategories *response.ArticleCategoriesResponse, err error) { + result, err := _i.Repo.FindOneBySlug(slug) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.ArticleCategoriesResponseMapper(result, host), nil +} + +func (_i *articleCategoriesService) Save(req request.ArticleCategoriesCreateRequest, authToken string) (articleCategories *entity.ArticleCategories, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if req.CreatedById != nil { + createdBy, err := _i.UsersRepo.FindOne(*req.CreatedById) + if err != nil { + return nil, err + } + newReq.CreatedById = &createdBy.ID + } else { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + } + + return _i.Repo.Create(newReq) +} + +func (_i *articleCategoriesService) SaveThumbnail(c *fiber.Ctx) (err error) { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + if err != nil { + return err + } + files := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + // Iterasi semua file yang diunggah + for _, file := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1"). + Interface("data", file).Msg("") + + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(file.Filename) + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(file.Filename)[1:] + + rand.New(rand.NewSource(time.Now().UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := "articles/category/thumbnail/" + newFilename + + findCategory, err := _i.Repo.FindOne(uint(id)) + findCategory.ThumbnailPath = &objectName + err = _i.Repo.Update(uint(id), findCategory) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, file.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + + return +} + +func (_i *articleCategoriesService) Update(id uint, req request.ArticleCategoriesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + if req.CreatedById != nil { + createdBy, err := _i.UsersRepo.FindOne(*req.CreatedById) + if err != nil { + return err + } + newReq.CreatedById = &createdBy.ID + } + + return _i.Repo.Update(id, newReq) +} + +func (_i *articleCategoriesService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + + return _i.Repo.Update(id, result) +} + +func (_i *articleCategoriesService) Viewer(c *fiber.Ctx) (err error) { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + result, err := _i.Repo.FindOne(uint(id)) + if err != nil { + return err + } + + if result.ThumbnailPath == nil { + return nil + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := result.ThumbnailPath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). + Interface("data", objectName).Msg("") + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, *objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + contentType := mime.TypeByExtension("." + getFileExtension(*objectName)) + if contentType == "" { + contentType = "application/octet-stream" + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} diff --git a/app/module/article_category_details/article_category_details.module.go b/app/module/article_category_details/article_category_details.module.go new file mode 100644 index 0000000..3e3b966 --- /dev/null +++ b/app/module/article_category_details/article_category_details.module.go @@ -0,0 +1,54 @@ +package article_category_details + +import ( + "campaign-pool-be/app/module/article_category_details/controller" + "campaign-pool-be/app/module/article_category_details/repository" + "campaign-pool-be/app/module/article_category_details/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ArticleCategoryDetailsRouter +type ArticleCategoryDetailsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleCategoryDetails module +var NewArticleCategoryDetailsModule = fx.Options( + // register repository of ArticleCategoryDetails module + fx.Provide(repository.NewArticleCategoryDetailsRepository), + + // register service of ArticleCategoryDetails module + fx.Provide(service.NewArticleCategoryDetailsService), + + // register controller of ArticleCategoryDetails module + fx.Provide(controller.NewController), + + // register router of ArticleCategoryDetails module + fx.Provide(NewArticleCategoryDetailsRouter), +) + +// init ArticleCategoryDetailsRouter +func NewArticleCategoryDetailsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCategoryDetailsRouter { + return &ArticleCategoryDetailsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleCategoryDetails module +func (_i *ArticleCategoryDetailsRouter) RegisterArticleCategoryDetailsRoutes() { + // define controllers + articleCategoryDetailsController := _i.Controller.ArticleCategoryDetails + + // define routes + _i.App.Route("/article-category-details", func(router fiber.Router) { + router.Get("/", articleCategoryDetailsController.All) + router.Get("/:id", articleCategoryDetailsController.Show) + router.Post("/", articleCategoryDetailsController.Save) + router.Put("/:id", articleCategoryDetailsController.Update) + router.Delete("/:id", articleCategoryDetailsController.Delete) + }) +} diff --git a/app/module/article_category_details/controller/article_category_details.controller.go b/app/module/article_category_details/controller/article_category_details.controller.go new file mode 100644 index 0000000..c5fbcde --- /dev/null +++ b/app/module/article_category_details/controller/article_category_details.controller.go @@ -0,0 +1,185 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_category_details/request" + "campaign-pool-be/app/module/article_category_details/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type articleCategoryDetailsController struct { + articleCategoryDetailsService service.ArticleCategoryDetailsService +} + +type ArticleCategoryDetailsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewArticleCategoryDetailsController(articleCategoryDetailsService service.ArticleCategoryDetailsService) ArticleCategoryDetailsController { + return &articleCategoryDetailsController{ + articleCategoryDetailsService: articleCategoryDetailsService, + } +} + +// All get all ArticleCategoryDetails +// @Summary Get all ArticleCategoryDetails +// @Description API for getting all ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details [get] +func (_i *articleCategoryDetailsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.ArticleCategoryDetailsQueryRequest + req.Pagination = paginate + + articleCategoryDetailsData, paging, err := _i.articleCategoryDetailsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails list successfully retrieved"}, + Data: articleCategoryDetailsData, + Meta: paging, + }) +} + +// Show get one ArticleCategoryDetails +// @Summary Get one ArticleCategoryDetails +// @Description API for getting one ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param id path int true "ArticleCategoryDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details/{id} [get] +func (_i *articleCategoryDetailsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCategoryDetailsData, err := _i.articleCategoryDetailsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully retrieved"}, + Data: articleCategoryDetailsData, + }) +} + +// Save create ArticleCategoryDetails +// @Summary Create ArticleCategoryDetails +// @Description API for create ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Body request.ArticleCategoryDetailsCreateRequest +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details [post] +func (_i *articleCategoryDetailsController) Save(c *fiber.Ctx) error { + req := new(request.ArticleCategoryDetailsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.articleCategoryDetailsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully created"}, + }) +} + +// Update update ArticleCategoryDetails +// @Summary update ArticleCategoryDetails +// @Description API for update ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Body request.ArticleCategoryDetailsUpdateRequest +// @Param id path int true "ArticleCategoryDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details/{id} [put] +func (_i *articleCategoryDetailsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCategoryDetailsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleCategoryDetailsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully updated"}, + }) +} + +// Delete delete ArticleCategoryDetails +// @Summary delete ArticleCategoryDetails +// @Description API for delete ArticleCategoryDetails +// @Tags Untags +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "ArticleCategoryDetails ID" +// @Success 200 {object} response.Response +// @Failure 401 {object} response.Response +// @Failure 404 {object} response.Response +// @Failure 422 {object} response.Response +// @Failure 500 {object} response.Response +// @Router /article-category-details/{id} [delete] +func (_i *articleCategoryDetailsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleCategoryDetailsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"ArticleCategoryDetails successfully deleted"}, + }) +} diff --git a/app/module/article_category_details/controller/controller.go b/app/module/article_category_details/controller/controller.go new file mode 100644 index 0000000..83e2069 --- /dev/null +++ b/app/module/article_category_details/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/article_category_details/service" + +type Controller struct { + ArticleCategoryDetails ArticleCategoryDetailsController +} + +func NewController(ArticleCategoryDetailsService service.ArticleCategoryDetailsService) *Controller { + return &Controller{ + ArticleCategoryDetails: NewArticleCategoryDetailsController(ArticleCategoryDetailsService), + } +} diff --git a/app/module/article_category_details/mapper/article_category_details.mapper.go b/app/module/article_category_details/mapper/article_category_details.mapper.go new file mode 100644 index 0000000..7c0466e --- /dev/null +++ b/app/module/article_category_details/mapper/article_category_details.mapper.go @@ -0,0 +1,21 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity/article_category_details" + res "campaign-pool-be/app/module/article_category_details/response" +) + +func ArticleCategoryDetailsResponseMapper(articleCategoryDetailsReq *article_category_details.ArticleCategoryDetails) (articleCategoryDetailsRes *res.ArticleCategoryDetailsResponse) { + if articleCategoryDetailsReq != nil { + + articleCategoryDetailsRes = &res.ArticleCategoryDetailsResponse{ + ID: articleCategoryDetailsReq.ID, + ArticleId: articleCategoryDetailsReq.ArticleId, + CategoryId: articleCategoryDetailsReq.CategoryId, + IsActive: articleCategoryDetailsReq.IsActive, + CreatedAt: articleCategoryDetailsReq.CreatedAt, + UpdatedAt: articleCategoryDetailsReq.UpdatedAt, + } + } + return articleCategoryDetailsRes +} diff --git a/app/module/article_category_details/repository/article_category_details.repository.go b/app/module/article_category_details/repository/article_category_details.repository.go new file mode 100644 index 0000000..d0d8956 --- /dev/null +++ b/app/module/article_category_details/repository/article_category_details.repository.go @@ -0,0 +1,78 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity/article_category_details" + "campaign-pool-be/app/module/article_category_details/request" + "campaign-pool-be/utils/paginator" +) + +type articleCategoryDetailsRepository struct { + DB *database.Database +} + +// ArticleCategoryDetailsRepository define interface of IArticleCategoryDetailsRepository +type ArticleCategoryDetailsRepository interface { + GetAll(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, paging paginator.Pagination, err error) + FindOne(id uint) (articleCategoryDetails *article_category_details.ArticleCategoryDetails, err error) + FindByArticleId(articleId uint) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, err error) + Create(articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) + Update(id uint, articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) + Delete(id uint) (err error) +} + +func NewArticleCategoryDetailsRepository(db *database.Database) ArticleCategoryDetailsRepository { + return &articleCategoryDetailsRepository{ + DB: db, + } +} + +// implement interface of IArticleCategoryDetailsRepository +func (_i *articleCategoryDetailsRepository) GetAll(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&article_category_details.ArticleCategoryDetails{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCategoryDetailss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCategoryDetailsRepository) FindOne(id uint) (articleCategoryDetails *article_category_details.ArticleCategoryDetails, err error) { + if err := _i.DB.DB.First(&articleCategoryDetails, id).Error; err != nil { + return nil, err + } + + return articleCategoryDetails, nil +} + +func (_i *articleCategoryDetailsRepository) FindByArticleId(articleId uint) (articleCategoryDetailss []*article_category_details.ArticleCategoryDetails, err error) { + if err := _i.DB.DB.Where("article_id = ?", articleId).Preload("Category").Find(&articleCategoryDetailss).Error; err != nil { + return nil, err + } + + return articleCategoryDetailss, nil +} + +func (_i *articleCategoryDetailsRepository) Create(articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) { + return _i.DB.DB.Create(articleCategoryDetails).Error +} + +func (_i *articleCategoryDetailsRepository) Update(id uint, articleCategoryDetails *article_category_details.ArticleCategoryDetails) (err error) { + return _i.DB.DB.Model(&article_category_details.ArticleCategoryDetails{}). + Where(&article_category_details.ArticleCategoryDetails{ID: id}). + Updates(articleCategoryDetails).Error +} + +func (_i *articleCategoryDetailsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&article_category_details.ArticleCategoryDetails{}, id).Error +} diff --git a/app/module/article_category_details/request/article_category_details.request.go b/app/module/article_category_details/request/article_category_details.request.go new file mode 100644 index 0000000..0430107 --- /dev/null +++ b/app/module/article_category_details/request/article_category_details.request.go @@ -0,0 +1,52 @@ +package request + +import ( + "campaign-pool-be/app/database/entity/article_category_details" + "campaign-pool-be/utils/paginator" + "time" +) + +type ArticleCategoryDetailsGeneric interface { + ToEntity() +} + +type ArticleCategoryDetailsQueryRequest struct { + ArticleId int `json:"articleId" validate:"required"` + CategoryId int `json:"categoryId" validate:"required"` + IsActive bool `json:"isActive" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCategoryDetailsCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` + CategoryId int `json:"categoryId" validate:"required"` + IsActive bool `json:"isActive" validate:"required"` +} + +func (req ArticleCategoryDetailsCreateRequest) ToEntity() *article_category_details.ArticleCategoryDetails { + return &article_category_details.ArticleCategoryDetails{ + ArticleId: req.ArticleId, + CategoryId: req.CategoryId, + IsActive: req.IsActive, + } +} + +type ArticleCategoryDetailsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + CategoryId int `json:"categoryId" validate:"required"` + IsActive bool `json:"isActive" validate:"required"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (req ArticleCategoryDetailsUpdateRequest) ToEntity() *article_category_details.ArticleCategoryDetails { + return &article_category_details.ArticleCategoryDetails{ + ID: req.ID, + ArticleId: req.ArticleId, + CategoryId: req.CategoryId, + IsActive: req.IsActive, + CreatedAt: req.CreatedAt, + UpdatedAt: req.UpdatedAt, + } +} diff --git a/app/module/article_category_details/response/article_category_details.response.go b/app/module/article_category_details/response/article_category_details.response.go new file mode 100644 index 0000000..3daadb9 --- /dev/null +++ b/app/module/article_category_details/response/article_category_details.response.go @@ -0,0 +1,12 @@ +package response + +import "time" + +type ArticleCategoryDetailsResponse struct { + ID uint `json:"id"` + ArticleId uint `json:"articleId"` + CategoryId int `json:"categoryId"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_category_details/service/article_category_details.service.go b/app/module/article_category_details/service/article_category_details.service.go new file mode 100644 index 0000000..7f5fa51 --- /dev/null +++ b/app/module/article_category_details/service/article_category_details.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "campaign-pool-be/app/module/article_category_details/mapper" + "campaign-pool-be/app/module/article_category_details/repository" + "campaign-pool-be/app/module/article_category_details/request" + "campaign-pool-be/app/module/article_category_details/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// ArticleCategoryDetailsService +type articleCategoryDetailsService struct { + Repo repository.ArticleCategoryDetailsRepository + Log zerolog.Logger +} + +// ArticleCategoryDetailsService define interface of IArticleCategoryDetailsService +type ArticleCategoryDetailsService interface { + All(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetails []*response.ArticleCategoryDetailsResponse, paging paginator.Pagination, err error) + Show(id uint) (articleCategoryDetails *response.ArticleCategoryDetailsResponse, err error) + Save(req request.ArticleCategoryDetailsCreateRequest) (err error) + Update(id uint, req request.ArticleCategoryDetailsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewArticleCategoryDetailsService init ArticleCategoryDetailsService +func NewArticleCategoryDetailsService(repo repository.ArticleCategoryDetailsRepository, log zerolog.Logger) ArticleCategoryDetailsService { + + return &articleCategoryDetailsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of ArticleCategoryDetailsService +func (_i *articleCategoryDetailsService) All(req request.ArticleCategoryDetailsQueryRequest) (articleCategoryDetailss []*response.ArticleCategoryDetailsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleCategoryDetailss = append(articleCategoryDetailss, mapper.ArticleCategoryDetailsResponseMapper(result)) + } + + return +} + +func (_i *articleCategoryDetailsService) Show(id uint) (articleCategoryDetails *response.ArticleCategoryDetailsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleCategoryDetailsResponseMapper(result), nil +} + +func (_i *articleCategoryDetailsService) Save(req request.ArticleCategoryDetailsCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *articleCategoryDetailsService) Update(id uint, req request.ArticleCategoryDetailsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleCategoryDetailsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/article_comments/article_comments.module.go b/app/module/article_comments/article_comments.module.go new file mode 100644 index 0000000..b7f722f --- /dev/null +++ b/app/module/article_comments/article_comments.module.go @@ -0,0 +1,54 @@ +package article_comments + +import ( + "campaign-pool-be/app/module/article_comments/controller" + "campaign-pool-be/app/module/article_comments/repository" + "campaign-pool-be/app/module/article_comments/service" + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ArticleCommentsRouter +type ArticleCommentsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleComments module +var NewArticleCommentsModule = fx.Options( + // register repository of ArticleComments module + fx.Provide(repository.NewArticleCommentsRepository), + + // register service of ArticleComments module + fx.Provide(service.NewArticleCommentsService), + + // register controller of ArticleComments module + fx.Provide(controller.NewController), + + // register router of ArticleComments module + fx.Provide(NewArticleCommentsRouter), +) + +// init ArticleCommentsRouter +func NewArticleCommentsRouter(fiber *fiber.App, controller *controller.Controller) *ArticleCommentsRouter { + return &ArticleCommentsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleComments module +func (_i *ArticleCommentsRouter) RegisterArticleCommentsRoutes() { + // define controllers + articleCommentsController := _i.Controller.ArticleComments + + // define routes + _i.App.Route("/article-comments", func(router fiber.Router) { + router.Get("/", articleCommentsController.All) + router.Get("/:id", articleCommentsController.Show) + router.Post("/", articleCommentsController.Save) + router.Put("/:id", articleCommentsController.Update) + router.Delete("/:id", articleCommentsController.Delete) + router.Post("/approval", articleCommentsController.Approval) + }) +} diff --git a/app/module/article_comments/controller/article_comments.controller.go b/app/module/article_comments/controller/article_comments.controller.go new file mode 100644 index 0000000..a88c1d0 --- /dev/null +++ b/app/module/article_comments/controller/article_comments.controller.go @@ -0,0 +1,235 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_comments/request" + "campaign-pool-be/app/module/article_comments/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type articleCommentsController struct { + articleCommentsService service.ArticleCommentsService + Log zerolog.Logger +} + +type ArticleCommentsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Approval(c *fiber.Ctx) error +} + +func NewArticleCommentsController(articleCommentsService service.ArticleCommentsService, log zerolog.Logger) ArticleCommentsController { + return &articleCommentsController{ + articleCommentsService: articleCommentsService, + Log: log, + } +} + +// All get all ArticleComments +// @Summary Get all ArticleComments +// @Description API for getting all ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param req query request.ArticleCommentsQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments [get] +func (_i *articleCommentsController) All(c *fiber.Ctx) error { + // Get from context + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleCommentsQueryRequestContext{ + Message: c.Query("message"), + ArticleId: c.Query("articleId"), + CommentFrom: c.Query("commentFrom"), + ParentId: c.Query("parentId"), + IsPublic: c.Query("isPublic"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleCommentsData, paging, err := _i.articleCommentsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments list successfully retrieved"}, + Data: articleCommentsData, + Meta: paging, + }) +} + +// Show get one ArticleComments +// @Summary Get one ArticleComments +// @Description API for getting one ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [get] +func (_i *articleCommentsController) Show(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleCommentsData, err := _i.articleCommentsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully retrieved"}, + Data: articleCommentsData, + }) +} + +// Save create ArticleComments +// @Summary Create ArticleComments +// @Description API for create ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticleCommentsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments [post] +func (_i *articleCommentsController) Save(c *fiber.Ctx) error { + // Get from context + req := new(request.ArticleCommentsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.articleCommentsService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully created"}, + Data: dataResult, + }) +} + +// Update update ArticleComments +// @Summary update ArticleComments +// @Description API for update ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.ArticleCommentsUpdateRequest true "Required payload" +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [put] +func (_i *articleCommentsController) Update(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleCommentsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleCommentsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully updated"}, + }) +} + +// Delete delete ArticleComments +// @Summary delete ArticleComments +// @Description API for delete ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "ArticleComments ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/{id} [delete] +func (_i *articleCommentsController) Delete(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleCommentsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully deleted"}, + }) +} + +// Approval ArticleComments +// @Summary Approval ArticleComments +// @Description API for Approval ArticleComments +// @Tags ArticleComments +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.ArticleCommentsApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-comments/approval [post] +func (_i *articleCommentsController) Approval(c *fiber.Ctx) error { + // Get from context + req := new(request.ArticleCommentsApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.articleCommentsService.Approval(req.ID, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleComments successfully reviewed"}, + }) +} diff --git a/app/module/article_comments/controller/controller.go b/app/module/article_comments/controller/controller.go new file mode 100644 index 0000000..ee6a48b --- /dev/null +++ b/app/module/article_comments/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_comments/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + ArticleComments ArticleCommentsController +} + +func NewController(ArticleCommentsService service.ArticleCommentsService, log zerolog.Logger) *Controller { + return &Controller{ + ArticleComments: NewArticleCommentsController(ArticleCommentsService, log), + } +} diff --git a/app/module/article_comments/mapper/article_comments.mapper.go b/app/module/article_comments/mapper/article_comments.mapper.go new file mode 100644 index 0000000..1075f22 --- /dev/null +++ b/app/module/article_comments/mapper/article_comments.mapper.go @@ -0,0 +1,35 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/article_comments/response" + usersRepository "campaign-pool-be/app/module/users/repository" +) + +func ArticleCommentsResponseMapper(articleCommentsReq *entity.ArticleComments, usersRepo usersRepository.UsersRepository) (articleCommentsRes *res.ArticleCommentsResponse) { + if articleCommentsReq != nil { + + commentFromName := "" + if articleCommentsReq.CommentFrom != nil { + findUser, _ := usersRepo.FindOne(*articleCommentsReq.CommentFrom) + if findUser != nil { + commentFromName = findUser.Fullname + } + } + + articleCommentsRes = &res.ArticleCommentsResponse{ + ID: articleCommentsReq.ID, + Message: articleCommentsReq.Message, + ArticleId: articleCommentsReq.ArticleId, + CommentFromId: articleCommentsReq.CommentFrom, + CommentFromName: &commentFromName, + ParentId: articleCommentsReq.ParentId, + IsPublic: articleCommentsReq.IsPublic, + StatusId: articleCommentsReq.StatusId, + IsActive: articleCommentsReq.IsActive, + CreatedAt: articleCommentsReq.CreatedAt, + UpdatedAt: articleCommentsReq.UpdatedAt, + } + } + return articleCommentsRes +} diff --git a/app/module/article_comments/repository/article_comments.repository.go b/app/module/article_comments/repository/article_comments.repository.go new file mode 100644 index 0000000..e794165 --- /dev/null +++ b/app/module/article_comments/repository/article_comments.repository.go @@ -0,0 +1,108 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_comments/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type articleCommentsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticleCommentsRepository define interface of IArticleCommentsRepository +type ArticleCommentsRepository interface { + GetAll(req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) + FindOne(id uint) (articleComments *entity.ArticleComments, err error) + Create(articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) + Update(id uint, articleComments *entity.ArticleComments) (err error) + Delete(id uint) (err error) +} + +func NewArticleCommentsRepository(db *database.Database, logger zerolog.Logger) ArticleCommentsRepository { + return &articleCommentsRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IArticleCommentsRepository +func (_i *articleCommentsRepository) GetAll(req request.ArticleCommentsQueryRequest) (articleCommentss []*entity.ArticleComments, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleComments{}) + query = query.Where("is_active = ?", true) + + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.CommentFrom != nil { + query = query.Where("comment_from = ?", req.CommentFrom) + } + if req.ParentId != nil { + query = query.Where("parent_id = ?", req.ParentId) + } + if req.IsPublic != nil { + query = query.Where("is_public = ?", req.IsPublic) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleCommentss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleCommentsRepository) FindOne(id uint) (articleComments *entity.ArticleComments, err error) { + query := _i.DB.DB + if err := query.First(&articleComments, id).Error; err != nil { + return nil, err + } + + return articleComments, nil +} + +func (_i *articleCommentsRepository) Create(articleComments *entity.ArticleComments) (articleCommentsReturn *entity.ArticleComments, err error) { + result := _i.DB.DB.Create(articleComments) + return articleComments, result.Error +} + +func (_i *articleCommentsRepository) Update(id uint, articleComments *entity.ArticleComments) (err error) { + articleCommentsMap, err := utilSvc.StructToMap(articleComments) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.ArticleComments{}). + Where(&entity.ArticleComments{ID: id}). + Updates(articleCommentsMap).Error +} + +func (_i *articleCommentsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.ArticleComments{}, id).Error +} diff --git a/app/module/article_comments/request/article_comments.request.go b/app/module/article_comments/request/article_comments.request.go new file mode 100644 index 0000000..8eddba9 --- /dev/null +++ b/app/module/article_comments/request/article_comments.request.go @@ -0,0 +1,111 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type ArticleCommentsGeneric interface { + ToEntity() +} + +type ArticleCommentsQueryRequest struct { + Message *string `json:"message"` + ArticleId *int `json:"articleId"` + CommentFrom *int `json:"commentFrom"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleCommentsCreateRequest struct { + Message string `json:"message" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` +} + +func (req ArticleCommentsCreateRequest) ToEntity() *entity.ArticleComments { + return &entity.ArticleComments{ + Message: req.Message, + ArticleId: req.ArticleId, + ParentId: req.ParentId, + StatusId: 0, + } +} + +type ArticleCommentsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Message string `json:"message" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + ParentId *int `json:"parentId"` + IsPublic *bool `json:"isPublic"` +} + +func (req ArticleCommentsUpdateRequest) ToEntity() *entity.ArticleComments { + return &entity.ArticleComments{ + ID: req.ID, + Message: req.Message, + ArticleId: req.ArticleId, + ParentId: req.ParentId, + UpdatedAt: time.Now(), + } +} + +type ArticleCommentsApprovalRequest struct { + ID uint `json:"id" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req ArticleCommentsApprovalRequest) ToEntity() *entity.ArticleComments { + approvedNow := time.Now() + return &entity.ArticleComments{ + ID: req.ID, + StatusId: req.StatusId, + ApprovedAt: &approvedNow, + } +} + +type ArticleCommentsQueryRequestContext struct { + Message string `json:"message"` + ArticleId string `json:"articleId"` + CommentFrom string `json:"commentFrom"` + ParentId string `json:"parentId"` + IsPublic string `json:"isPublic"` +} + +func (req ArticleCommentsQueryRequestContext) ToParamRequest() ArticleCommentsQueryRequest { + var request ArticleCommentsQueryRequest + + if message := req.Message; message != "" { + request.Message = &message + } + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if commentFromStr := req.CommentFrom; commentFromStr != "" { + commentFrom, err := strconv.Atoi(commentFromStr) + if err == nil { + request.CommentFrom = &commentFrom + } + } + if parentIdStr := req.ParentId; parentIdStr != "" { + parentId, err := strconv.Atoi(parentIdStr) + if err == nil { + request.ParentId = &parentId + } + } + if isPublicStr := req.IsPublic; isPublicStr != "" { + isPublic, err := strconv.ParseBool(isPublicStr) + if err == nil { + request.IsPublic = &isPublic + } + } + + return request +} diff --git a/app/module/article_comments/response/article_comments.response.go b/app/module/article_comments/response/article_comments.response.go new file mode 100644 index 0000000..ff86554 --- /dev/null +++ b/app/module/article_comments/response/article_comments.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type ArticleCommentsResponse struct { + ID uint `json:"id"` + Message string `json:"message"` + ArticleId uint `json:"articleId"` + CommentFromId *uint `json:"commentFromId"` + CommentFromName *string `json:"commentFromName"` + ParentId *int `json:"parentId"` + StatusId int `json:"statusId"` + IsPublic bool `json:"isPublic"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_comments/service/article_comments.service.go b/app/module/article_comments/service/article_comments.service.go new file mode 100644 index 0000000..3809716 --- /dev/null +++ b/app/module/article_comments/service/article_comments.service.go @@ -0,0 +1,105 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_comments/mapper" + "campaign-pool-be/app/module/article_comments/repository" + "campaign-pool-be/app/module/article_comments/request" + "campaign-pool-be/app/module/article_comments/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" + + utilSvc "campaign-pool-be/utils/service" +) + +// ArticleCommentsService +type articleCommentsService struct { + Repo repository.ArticleCommentsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// ArticleCommentsService define interface of IArticleCommentsService +type ArticleCommentsService interface { + All(req request.ArticleCommentsQueryRequest) (articleComments []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) + Show(id uint) (articleComments *response.ArticleCommentsResponse, err error) + Save(req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) + Update(id uint, req request.ArticleCommentsUpdateRequest) (err error) + Delete(id uint) error + Approval(id uint, req request.ArticleCommentsApprovalRequest) (err error) +} + +// NewArticleCommentsService init ArticleCommentsService +func NewArticleCommentsService(repo repository.ArticleCommentsRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) ArticleCommentsService { + + return &articleCommentsService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of ArticleCommentsService +func (_i *articleCommentsService) All(req request.ArticleCommentsQueryRequest) (articleCommentss []*response.ArticleCommentsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + articleCommentss = append(articleCommentss, mapper.ArticleCommentsResponseMapper(result, _i.UsersRepo)) + } + + return +} + +func (_i *articleCommentsService) Show(id uint) (articleComments *response.ArticleCommentsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ArticleCommentsResponseMapper(result, _i.UsersRepo), nil +} + +func (_i *articleCommentsService) Save(req request.ArticleCommentsCreateRequest, authToken string) (articleComments *entity.ArticleComments, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CommentFrom = &createdBy.ID + newReq.IsActive = true + + return _i.Repo.Create(newReq) +} + +func (_i *articleCommentsService) Update(id uint, req request.ArticleCommentsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleCommentsService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(id, result) +} + +func (_i *articleCommentsService) Approval(id uint, req request.ArticleCommentsApprovalRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if newReq.StatusId == 1 || newReq.StatusId == 2 { + newReq.IsPublic = true + } else { + newReq.IsPublic = false + } + + return _i.Repo.Update(id, newReq) +} diff --git a/app/module/article_files/article_files.module.go b/app/module/article_files/article_files.module.go new file mode 100644 index 0000000..ce60ace --- /dev/null +++ b/app/module/article_files/article_files.module.go @@ -0,0 +1,58 @@ +package article_files + +import ( + "campaign-pool-be/app/module/article_files/controller" + "campaign-pool-be/app/module/article_files/repository" + "campaign-pool-be/app/module/article_files/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ArticleFilesRouter +type ArticleFilesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ArticleFiles module +var NewArticleFilesModule = fx.Options( + // register repository of ArticleFiles module + fx.Provide(repository.NewArticleFilesRepository), + + // register service of ArticleFiles module + fx.Provide(service.NewArticleFilesService), + fx.Provide(service.NewUploadService), + fx.Provide(service.NewUploadManager), + + // register controller of ArticleFiles module + fx.Provide(controller.NewController), + + // register router of ArticleFiles module + fx.Provide(NewArticleFilesRouter), +) + +// init ArticleFilesRouter +func NewArticleFilesRouter(fiber *fiber.App, controller *controller.Controller) *ArticleFilesRouter { + return &ArticleFilesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ArticleFiles module +func (_i *ArticleFilesRouter) RegisterArticleFilesRoutes() { + // define controllers + articleFilesController := _i.Controller.ArticleFiles + + // define routes + _i.App.Route("/article-files", func(router fiber.Router) { + router.Get("/", articleFilesController.All) + router.Get("/:id", articleFilesController.Show) + router.Post("/:articleId", articleFilesController.Save) + router.Put("/:id", articleFilesController.Update) + router.Delete("/:id", articleFilesController.Delete) + router.Get("/viewer/:filename", articleFilesController.Viewer) + router.Get("/upload-status/:uploadId", articleFilesController.GetUploadStatus) + }) +} diff --git a/app/module/article_files/controller/article_files.controller.go b/app/module/article_files/controller/article_files.controller.go new file mode 100644 index 0000000..1c8e4b7 --- /dev/null +++ b/app/module/article_files/controller/article_files.controller.go @@ -0,0 +1,240 @@ +package controller + +import ( + "campaign-pool-be/app/module/article_files/request" + "campaign-pool-be/app/module/article_files/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "fmt" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type articleFilesController struct { + articleFilesService service.ArticleFilesService +} + +type ArticleFilesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + GetUploadStatus(c *fiber.Ctx) error +} + +func NewArticleFilesController(articleFilesService service.ArticleFilesService) ArticleFilesController { + return &articleFilesController{ + articleFilesService: articleFilesService, + } +} + +// All ArticleFiles +// @Summary Get all ArticleFiles +// @Description API for getting all ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param req query request.ArticleFilesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files [get] +func (_i *articleFilesController) All(c *fiber.Ctx) error { + // Get from context + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticleFilesQueryRequestContext{ + ArticleId: c.Query("articleId"), + FileName: c.Query("fileName"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articleFilesData, paging, err := _i.articleFilesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles list successfully retrieved"}, + Data: articleFilesData, + Meta: paging, + }) +} + +// Show ArticleFiles +// @Summary Get one ArticleFiles +// @Description API for getting one ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param id path int true "ArticleFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{id} [get] +func (_i *articleFilesController) Show(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + articleFilesData, err := _i.articleFilesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully retrieved"}, + Data: articleFilesData, + }) +} + +// Save ArticleFiles +// @Summary Upload ArticleFiles +// @Description API for create ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param files formData file true "Upload file" multiple true +// @Param articleId path int true "Article ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{articleId} [post] +func (_i *articleFilesController) Save(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("articleId"), 10, 0) + if err != nil { + return err + } + + err = _i.articleFilesService.Save(c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully upload"}, + }) +} + +// Update ArticleFiles +// @Summary Update ArticleFiles +// @Description API for update ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.ArticleFilesUpdateRequest true "Required payload" +// @Param id path int true "ArticleFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{id} [put] +func (_i *articleFilesController) Update(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticleFilesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.articleFilesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully updated"}, + }) +} + +// Delete ArticleFiles +// @Summary Delete ArticleFiles +// @Description API for delete ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "ArticleFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/{id} [delete] +func (_i *articleFilesController) Delete(c *fiber.Ctx) error { + // Get from context + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.articleFilesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleFiles successfully deleted"}, + }) +} + +// Viewer ArticleFiles +// @Summary Viewer ArticleFiles +// @Description API for Viewer ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param filename path string true "Article File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/viewer/{filename} [get] +func (_i *articleFilesController) Viewer(c *fiber.Ctx) error { + // Get from context + return _i.articleFilesService.Viewer(c) +} + +// GetUploadStatus ArticleFiles +// @Summary GetUploadStatus ArticleFiles +// @Description API for GetUploadStatus ArticleFiles +// @Tags Article Files +// @Security Bearer +// @Param uploadId path string true "Upload ID of ArticleFiles" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /article-files/upload-status/{uploadId} [get] +func (_i *articleFilesController) GetUploadStatus(c *fiber.Ctx) error { + progress, _ := _i.articleFilesService.GetUploadStatus(c) + progressMessage := fmt.Sprintf("Upload Progress: %d%%", progress) + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Upload Status Retrieve"}, + Data: progressMessage, + }) +} diff --git a/app/module/article_files/controller/controller.go b/app/module/article_files/controller/controller.go new file mode 100644 index 0000000..5666f11 --- /dev/null +++ b/app/module/article_files/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/article_files/service" + +type Controller struct { + ArticleFiles ArticleFilesController +} + +func NewController(ArticleFilesService service.ArticleFilesService) *Controller { + return &Controller{ + ArticleFiles: NewArticleFilesController(ArticleFilesService), + } +} diff --git a/app/module/article_files/mapper/article_files.mapper.go b/app/module/article_files/mapper/article_files.mapper.go new file mode 100644 index 0000000..886903b --- /dev/null +++ b/app/module/article_files/mapper/article_files.mapper.go @@ -0,0 +1,37 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/article_files/response" +) + +func ArticleFilesResponseMapper(articleFilesReq *entity.ArticleFiles, host string) (articleFilesRes *res.ArticleFilesResponse) { + fileUrl := host + "/article-files/viewer/" + if articleFilesReq.FileName != nil { + fileUrl += *articleFilesReq.FileName + } + + if articleFilesReq != nil { + articleFilesRes = &res.ArticleFilesResponse{ + ID: articleFilesReq.ID, + ArticleId: articleFilesReq.ArticleId, + FilePath: articleFilesReq.FilePath, + FileUrl: &fileUrl, + FileName: articleFilesReq.FileName, + FileThumbnail: articleFilesReq.FileThumbnail, + FileAlt: articleFilesReq.FileAlt, + WidthPixel: articleFilesReq.WidthPixel, + HeightPixel: articleFilesReq.HeightPixel, + Size: articleFilesReq.Size, + DownloadCount: articleFilesReq.DownloadCount, + CreatedById: articleFilesReq.CreatedById, + StatusId: articleFilesReq.StatusId, + IsPublish: articleFilesReq.IsPublish, + PublishedAt: articleFilesReq.PublishedAt, + IsActive: articleFilesReq.IsActive, + CreatedAt: articleFilesReq.CreatedAt, + UpdatedAt: articleFilesReq.UpdatedAt, + } + } + return articleFilesRes +} diff --git a/app/module/article_files/repository/article_files.repository.go b/app/module/article_files/repository/article_files.repository.go new file mode 100644 index 0000000..bc220ff --- /dev/null +++ b/app/module/article_files/repository/article_files.repository.go @@ -0,0 +1,123 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/article_files/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" +) + +type articleFilesRepository struct { + DB *database.Database +} + +// ArticleFilesRepository define interface of IArticleFilesRepository +type ArticleFilesRepository interface { + GetAll(req request.ArticleFilesQueryRequest) (articleFiless []*entity.ArticleFiles, paging paginator.Pagination, err error) + FindOne(id uint) (articleFiles *entity.ArticleFiles, err error) + FindByArticle(articleId uint) (articleFiles []*entity.ArticleFiles, err error) + FindByFilename(filename string) (articleFiles *entity.ArticleFiles, err error) + Create(articleFiles *entity.ArticleFiles) (err error) + Update(id uint, articleFiles *entity.ArticleFiles) (err error) + Delete(id uint) (err error) +} + +func NewArticleFilesRepository(db *database.Database) ArticleFilesRepository { + return &articleFilesRepository{ + DB: db, + } +} + +// implement interface of IArticleFilesRepository +func (_i *articleFilesRepository) GetAll(req request.ArticleFilesQueryRequest) (articleFiless []*entity.ArticleFiles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.ArticleFiles{}) + query = query.Where("is_active = ?", true) + + if req.ArticleId != nil { + query = query.Where("article_id = ?", req.ArticleId) + } + if req.FileName != nil && *req.FileName != "" { + fileName := strings.ToLower(*req.FileName) + query = query.Where("LOWER(file_name) LIKE ?", "%"+strings.ToLower(fileName)+"%") + } + if req.IsPublish != nil { + query = query.Where("is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articleFiless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articleFilesRepository) FindOne(id uint) (articleFiles *entity.ArticleFiles, err error) { + query := _i.DB.DB + + if err := query.First(&articleFiles, id).Error; err != nil { + return nil, err + } + + return articleFiles, nil +} + +func (_i *articleFilesRepository) FindByArticle(articleId uint) (articleFiles []*entity.ArticleFiles, err error) { + query := _i.DB.DB.Where("article_id = ?", articleId) + + if err := query.Find(&articleFiles).Error; err != nil { + return nil, err + } + + return articleFiles, nil +} + +func (_i *articleFilesRepository) FindByFilename(articleFilename string) (articleFiles *entity.ArticleFiles, err error) { + query := _i.DB.DB.Where("file_name = ?", articleFilename) + + if err := query.First(&articleFiles).Error; err != nil { + return nil, err + } + + return articleFiles, nil +} + +func (_i *articleFilesRepository) Create(articleFiles *entity.ArticleFiles) (err error) { + return _i.DB.DB.Create(articleFiles).Error +} + +func (_i *articleFilesRepository) Update(id uint, articleFiles *entity.ArticleFiles) (err error) { + articleFilesMap, err := utilSvc.StructToMap(articleFiles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.ArticleFiles{}). + Where(&entity.ArticleFiles{ID: id}). + Updates(articleFilesMap).Error +} + +func (_i *articleFilesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.ArticleFiles{}, id).Error +} diff --git a/app/module/article_files/request/article_files.request.go b/app/module/article_files/request/article_files.request.go new file mode 100644 index 0000000..9f1b9af --- /dev/null +++ b/app/module/article_files/request/article_files.request.go @@ -0,0 +1,120 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type ArticleFilesGeneric interface { + ToEntity() +} + +type ArticleFilesQueryRequest struct { + ArticleId *int `json:"articleId"` + FileName *string `json:"fileName"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticleFilesCreateRequest struct { + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + UploadId *string `json:"uploadId"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileThumbnail *string `json:"fileThumbnail"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` +} + +func (req ArticleFilesCreateRequest) ToEntity() *entity.ArticleFiles { + return &entity.ArticleFiles{ + ArticleId: req.ArticleId, + UploadID: req.UploadId, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileThumbnail: req.FileThumbnail, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + } +} + +type ArticleFilesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ArticleId uint `json:"articleId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileThumbnail *string `json:"fileThumbnail"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + IsPublish *bool `json:"isPublish" validate:"required"` + PublishedAt *time.Time `json:"publishedAt" validate:"required"` +} + +func (req ArticleFilesUpdateRequest) ToEntity() *entity.ArticleFiles { + return &entity.ArticleFiles{ + ID: req.ID, + ArticleId: req.ArticleId, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileThumbnail: req.FileThumbnail, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + UpdatedAt: time.Now(), + } +} + +type ArticleFilesQueryRequestContext struct { + ArticleId string `json:"articleId"` + FileName string `json:"fileName"` + StatusId string `json:"statusId"` + IsPublish string `json:"isPublish"` +} + +func (req ArticleFilesQueryRequestContext) ToParamRequest() ArticleFilesQueryRequest { + var request ArticleFilesQueryRequest + + if articleIdStr := req.ArticleId; articleIdStr != "" { + articleId, err := strconv.Atoi(articleIdStr) + if err == nil { + request.ArticleId = &articleId + } + } + if fileName := req.FileName; fileName != "" { + request.FileName = &fileName + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + + return request +} diff --git a/app/module/article_files/response/article_files.response.go b/app/module/article_files/response/article_files.response.go new file mode 100644 index 0000000..0993739 --- /dev/null +++ b/app/module/article_files/response/article_files.response.go @@ -0,0 +1,24 @@ +package response + +import "time" + +type ArticleFilesResponse struct { + ID uint `json:"id"` + ArticleId uint `json:"articleId"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileThumbnail *string `json:"fileThumbnail"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + DownloadCount *int `json:"downloadCount"` + CreatedById int `json:"createdById"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/article_files/service/article_files.service.go b/app/module/article_files/service/article_files.service.go new file mode 100644 index 0000000..a861dbe --- /dev/null +++ b/app/module/article_files/service/article_files.service.go @@ -0,0 +1,446 @@ +package service + +import ( + "campaign-pool-be/app/module/article_files/mapper" + "campaign-pool-be/app/module/article_files/repository" + "campaign-pool-be/app/module/article_files/request" + "campaign-pool-be/app/module/article_files/response" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + "context" + "fmt" + "io" + "log" + "math/rand" + "mime" + "mime/multipart" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// ArticleFilesService +type articleFilesService struct { + Repo repository.ArticleFilesRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage +} + +// ArticleFilesService define interface of IArticleFilesService +type ArticleFilesService interface { + All(req request.ArticleFilesQueryRequest) (articleFiles []*response.ArticleFilesResponse, paging paginator.Pagination, err error) + Show(id uint) (articleFiles *response.ArticleFilesResponse, err error) + Save(c *fiber.Ctx, id uint) error + SaveAsync(c *fiber.Ctx, id uint) error + Update(id uint, req request.ArticleFilesUpdateRequest) (err error) + GetUploadStatus(c *fiber.Ctx) (progress int, err error) + Delete(id uint) error + Viewer(c *fiber.Ctx) error +} + +// NewArticleFilesService init ArticleFilesService +func NewArticleFilesService(repo repository.ArticleFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) ArticleFilesService { + + return &articleFilesService{ + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + } +} + +var ( + progressMap = make(map[string]int) // Menyimpan progress upload per UploadID + progressLock = sync.Mutex{} +) + +type progressWriter struct { + uploadID string + totalSize int64 + uploadedSize *int64 +} + +// All implement interface of ArticleFilesService +func (_i *articleFilesService) All(req request.ArticleFilesQueryRequest) (articleFiless []*response.ArticleFilesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + host := _i.Cfg.App.Domain + + for _, result := range results { + articleFiless = append(articleFiless, mapper.ArticleFilesResponseMapper(result, host)) + } + + return +} + +func (_i *articleFilesService) Show(id uint) (articleFiles *response.ArticleFilesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticleFilesResponseMapper(result, host), nil +} + +func (_i *articleFilesService) SaveAsync(c *fiber.Ctx, id uint) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + ctx := context.Background() + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + uploadID := strconv.Itoa(randUniqueId) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("articles/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + fileSize := strconv.FormatInt(fileHeader.Size, 10) + fileSizeInt := fileHeader.Size + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("Start upload", uploadID).Msg("") + + req := request.ArticleFilesCreateRequest{ + ArticleId: id, + UploadId: &uploadID, + FilePath: &objectName, + FileName: &newFilename, + FileAlt: &filenameAlt, + Size: &fileSize, + } + + err = _i.Repo.Create(req.ToEntity()) + if err != nil { + return err + } + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + tempFilePath := fmt.Sprintf("/tmp/%s", newFilename) + tempFile, err := os.Create(tempFilePath) + if err != nil { + return err + } + defer tempFile.Close() + + // Copy file ke direktori sementara + _, err = io.Copy(tempFile, src) + if err != nil { + return err + } + + go uploadToMinIO(ctx, _i.Log, minioClient, uploadID, tempFilePath, bucketName, objectName, fileSizeInt) + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "User:All"). + Interface("data", "Successfully uploaded").Msg("") + + return +} + +func (_i *articleFilesService) Save(c *fiber.Ctx, id uint) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("articles/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + fileSize := strconv.FormatInt(fileHeader.Size, 10) + + req := request.ArticleFilesCreateRequest{ + ArticleId: id, + FilePath: &objectName, + FileName: &newFilename, + FileAlt: &filenameAlt, + Size: &fileSize, + } + + err = _i.Repo.Create(req.ToEntity()) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "User:All"). + Interface("data", "Successfully uploaded").Msg("") + + return +} + +func (_i *articleFilesService) Update(id uint, req request.ArticleFilesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *articleFilesService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + result.IsActive = false + return _i.Repo.Update(id, result) +} + +func (_i *articleFilesService) Viewer(c *fiber.Ctx) (err error) { + filename := c.Params("filename") + result, err := _i.Repo.FindByFilename(filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.FilePath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). + Interface("data", objectName).Msg("") + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + // Tentukan Content-Type berdasarkan ekstensi file + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} + +func uploadTempFile(log zerolog.Logger, fileHeader *multipart.FileHeader, filePath string) { + src, err := fileHeader.Open() + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-0"). + Interface("err", err).Msg("") + } + defer src.Close() + + tempFile, err := os.Create(filePath) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-1"). + Interface("err", err).Msg("") + } + defer tempFile.Close() + + // Copy file ke direktori sementara + _, err = io.Copy(tempFile, src) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-2"). + Interface("err", err).Msg("") + } +} + +func uploadToMinIO(ctx context.Context, log zerolog.Logger, minioClient *minio.Client, uploadID, filePath, bucketName string, objectName string, fileSize int64) { + file, err := os.Open(filePath) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-3"). + Interface("err", err).Msg("") + return + } + defer file.Close() + + // Upload file ke MinIO dengan progress tracking + uploadProgress := int64(0) + reader := io.TeeReader(file, &progressWriter{uploadID: uploadID, totalSize: fileSize, uploadedSize: &uploadProgress}) + + _, err = minioClient.PutObject(ctx, bucketName, objectName, reader, fileSize, minio.PutObjectOptions{}) + if err != nil { + + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-4"). + Interface("err", err).Msg("") + return + } + + // Upload selesai, update progress menjadi 100 + progressLock.Lock() + progressMap[uploadID] = 100 + progressLock.Unlock() + + go removeFileTemp(log, filePath) +} + +func removeFileTemp(log zerolog.Logger, filePath string) { + err := os.Remove(filePath) + if err != nil { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-5"). + Interface("Failed to remove temporary file", err).Msg("") + } else { + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:uploadToMinIO-6"). + Interface("err", "Temporary file removed").Msg("") + } +} + +func (p *progressWriter) Write(data []byte) (int, error) { + n := len(data) + progressLock.Lock() + defer progressLock.Unlock() + + *p.uploadedSize += int64(n) + progress := int(float64(*p.uploadedSize) / float64(p.totalSize) * 100) + + // Update progress di map + progressMap[p.uploadID] = progress + + return n, nil +} + +func (_i *articleFilesService) GetUploadStatus(c *fiber.Ctx) (progress int, err error) { + uploadID := c.Params("uploadId") + + // Ambil progress dari map + progressLock.Lock() + progress, _ = progressMap[uploadID] + progressLock.Unlock() + + return progress, nil +} diff --git a/app/module/article_files/service/async_uploader.service.go b/app/module/article_files/service/async_uploader.service.go new file mode 100644 index 0000000..17c627b --- /dev/null +++ b/app/module/article_files/service/async_uploader.service.go @@ -0,0 +1,139 @@ +package service + +import ( + "context" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" + "io" + "time" +) + +// AsyncUploader menangani proses upload secara asynchronous +type UploadService interface { + UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error +} + +type uploadService struct { + uploadManager UploadManager + Log zerolog.Logger +} + +func NewUploadService(uploadManager UploadManager, log zerolog.Logger) UploadService { + return &uploadService{ + uploadManager: uploadManager, + Log: log, + } +} + +func (u *uploadService) UploadFile(ctx context.Context, minioClient *minio.Client, uploadID string, reader io.Reader, bucketName string, objectName string, size int64, contentType string) error { + status := &UploadStatus{ + FileName: objectName, + Size: size, + Progress: 0, + Status: "uploading", + ObjectName: objectName, + BucketName: bucketName, + StartTime: time.Now(), + } + + u.uploadManager.Add(uploadID, status) + + u.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile"). + Interface("add status", status).Msg("") + + // Upload ke Minio + _, err := minioClient.PutObject( + ctx, + bucketName, + objectName, + reader, + size, + minio.PutObjectOptions{ + ContentType: contentType, + PartSize: 10 * 1024 * 1024, // 10MB part size + }, + ) + + if err != nil { + u.uploadManager.UpdateStatus(uploadID, "error", err) + + u.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "UploadService::UploadFile"). + Interface("error when upload", err).Msg("") + } + + u.uploadManager.UpdateStatus(uploadID, "completed", nil) + return nil +} + +//func (au *UploadService) UploadFile() { +// // Buat context dengan timeout +// ctx, cancel := context.WithTimeout(au.ctx, 30*time.Minute) +// defer cancel() +// +// // Buat reader dari byte slice +// reader := bytes.NewReader(au.fileData) +// pipeReader, pipeWriter := io.Pipe() +// +// au.progressMap.Store(au.uploadID, 0.0) +// +// // Start goroutine to read from reader and write to pipe +// go func() { +// defer pipeWriter.Close() +// buf := make([]byte, au.partSize) +// +// totalParts := int(reader.Size() / au.partSize) +// if reader.Size()%au.partSize != 0 { +// totalParts++ +// } +// +// for i := 0; i < totalParts; i++ { +// n, err := reader.Read(buf) +// if err != nil && err != io.EOF { +// log.Println("Error reading file:", err) +// return +// } +// +// if _, err := pipeWriter.Write(buf[:n]); err != nil { +// log.Println("Error writing to pipe:", err) +// return +// } +// +// progress := float64(i+1) / float64(totalParts) * 100 +// au.progressMap.Store(au.uploadID, progress) +// au.uploadManager.UpdateProgress(au.uploadID, int(progress)) +// } +// }() +// +// // Upload ke Minio +// _, err := au.minioClient.PutObject( +// ctx, +// au.bucketName, +// au.objectName, +// pipeReader, +// reader.Size(), +// minio.PutObjectOptions{ +// ContentType: au.contentType, +// PartSize: 10 * 1024 * 1024, // 10MB part size +// }, +// ) +// +// if err != nil { +// log.Println("Error uploading file:", err) +// au.progressMap.Store(au.uploadID, "error") +// return +// } +// +// fmt.Printf("Uploading process for %s", au.objectName) +// +// if err != nil { +// uploadManager.UpdateStatus(au.uploadID, "error", err) +// fmt.Printf("Upload error for %s: %v\n", au.objectName, err) +// return +// } +// +// au.progressMap.Store(au.uploadID, 100) +// au.uploadManager.UpdateProgress(au.uploadID, 100) +// au.uploadManager.UpdateStatus(au.uploadID, "completed", nil) +//} diff --git a/app/module/article_files/service/upload_manager.service.go b/app/module/article_files/service/upload_manager.service.go new file mode 100644 index 0000000..f02ec96 --- /dev/null +++ b/app/module/article_files/service/upload_manager.service.go @@ -0,0 +1,71 @@ +package service + +import ( + "sync" + "time" +) + +type UploadStatus struct { + FileName string `json:"fileName"` + Size int64 `json:"size"` + Progress int `json:"progress"` + Status string `json:"status"` + ObjectName string `json:"objectName"` + BucketName string `json:"bucketName"` + StartTime time.Time `json:"startTime"` + Error string `json:"error,omitempty"` +} + +type UploadManager interface { + Add(uploadID string, status *UploadStatus) + UpdateProgress(uploadID string, progress int) + UpdateStatus(uploadID string, status string, err error) + Get(uploadID string) (*UploadStatus, bool) +} + +type uploadManager struct { + uploads map[string]*UploadStatus + mutex sync.RWMutex +} + +func NewUploadManager() UploadManager { + return &uploadManager{ + uploads: make(map[string]*UploadStatus), + } +} + +// Add menambahkan status upload baru +func (um *uploadManager) Add(uploadID string, status *UploadStatus) { + um.mutex.Lock() + defer um.mutex.Unlock() + um.uploads[uploadID] = status +} + +// UpdateProgress memperbarui progress upload +func (um *uploadManager) UpdateProgress(uploadID string, progress int) { + um.mutex.Lock() + defer um.mutex.Unlock() + if status, exists := um.uploads[uploadID]; exists { + status.Progress = progress + } +} + +// UpdateStatus memperbarui status upload +func (um *uploadManager) UpdateStatus(uploadID string, status string, err error) { + um.mutex.Lock() + defer um.mutex.Unlock() + if upload, exists := um.uploads[uploadID]; exists { + upload.Status = status + if err != nil { + upload.Error = err.Error() + } + } +} + +// Get mendapatkan status upload berdasarkan ID +func (um *uploadManager) Get(uploadID string) (*UploadStatus, bool) { + um.mutex.RLock() + defer um.mutex.RUnlock() + status, exists := um.uploads[uploadID] + return status, exists +} diff --git a/app/module/articles/articles.module.go b/app/module/articles/articles.module.go new file mode 100644 index 0000000..297b5ba --- /dev/null +++ b/app/module/articles/articles.module.go @@ -0,0 +1,62 @@ +package articles + +import ( + "campaign-pool-be/app/module/articles/controller" + "campaign-pool-be/app/module/articles/repository" + "campaign-pool-be/app/module/articles/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// ArticlesRouter struct of ArticlesRouter +type ArticlesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewArticlesModule register bulky of Articles module +var NewArticlesModule = fx.Options( + // register repository of Articles module + fx.Provide(repository.NewArticlesRepository), + + // register service of Articles module + fx.Provide(service.NewArticlesService), + + // register controller of Articles module + fx.Provide(controller.NewController), + + // register router of Articles module + fx.Provide(NewArticlesRouter), +) + +// NewArticlesRouter init ArticlesRouter +func NewArticlesRouter(fiber *fiber.App, controller *controller.Controller) *ArticlesRouter { + return &ArticlesRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterArticlesRoutes register routes of Articles module +func (_i *ArticlesRouter) RegisterArticlesRoutes() { + // define controllers + articlesController := _i.Controller.Articles + + // define routes + _i.App.Route("/articles", func(router fiber.Router) { + router.Get("/", articlesController.All) + router.Get("/old-id/:id", articlesController.ShowByOldId) + router.Get("/:id", articlesController.Show) + router.Post("/", articlesController.Save) + router.Put("/:id", articlesController.Update) + router.Put("/banner/:id", articlesController.UpdateBanner) + router.Post("/thumbnail/:id", articlesController.SaveThumbnail) + router.Get("/thumbnail/viewer/:thumbnailName", articlesController.Viewer) + router.Post("/publish-scheduling", articlesController.PublishScheduling) + router.Delete("/:id", articlesController.Delete) + router.Get("/statistic/summary", articlesController.SummaryStats) + router.Get("/statistic/user-levels", articlesController.ArticlePerUserLevelStats) + router.Get("/statistic/monthly", articlesController.ArticleMonthlyStats) + }) +} diff --git a/app/module/articles/controller/articles.controller.go b/app/module/articles/controller/articles.controller.go new file mode 100644 index 0000000..0b8c6da --- /dev/null +++ b/app/module/articles/controller/articles.controller.go @@ -0,0 +1,452 @@ +package controller + +import ( + "campaign-pool-be/app/module/articles/request" + "campaign-pool-be/app/module/articles/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type articlesController struct { + articlesService service.ArticlesService + Log zerolog.Logger +} + +type ArticlesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByOldId(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + UpdateBanner(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + SummaryStats(c *fiber.Ctx) error + ArticlePerUserLevelStats(c *fiber.Ctx) error + ArticleMonthlyStats(c *fiber.Ctx) error + PublishScheduling(c *fiber.Ctx) error +} + +func NewArticlesController(articlesService service.ArticlesService, log zerolog.Logger) ArticlesController { + return &articlesController{ + articlesService: articlesService, + Log: log, + } +} + +// All Articles +// @Summary Get all Articles +// @Description API for getting all Articles +// @Tags Articles +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.ArticlesQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles [get] +func (_i *articlesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.ArticlesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + Tags: c.Query("tags"), + Category: c.Query("category"), + CategoryId: c.Query("categoryId"), + TypeId: c.Query("typeId"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + IsDraft: c.Query("isDraft"), + IsBanner: c.Query("isBanner"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + articlesData, paging, err := _i.articlesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles list successfully retrieved"}, + Data: articlesData, + Meta: paging, + }) +} + +// Show Articles +// @Summary Get one Articles +// @Description API for getting one Articles +// @Tags Articles +// @Security Bearer +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id} [get] +func (_i *articlesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get from context + articlesData, err := _i.articlesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully retrieved"}, + Data: articlesData, + }) +} + +// ShowByOldId Articles +// @Summary Get one Articles +// @Description API for getting one Articles +// @Tags Articles +// @Security Bearer +// @Param id path int true "Articles Old ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/old-id/{id} [get] +func (_i *articlesController) ShowByOldId(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get from context + articlesData, err := _i.articlesService.ShowByOldId(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully retrieved"}, + Data: articlesData, + }) +} + +// Save Articles +// @Summary Create Articles +// @Description API for create Articles +// @Tags Articles +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ArticlesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles [post] +func (_i *articlesController) Save(c *fiber.Ctx) error { + req := new(request.ArticlesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + // Get from context + dataResult, err := _i.articlesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully created"}, + Data: dataResult, + }) +} + +// SaveThumbnail Articles +// @Summary Save Thumbnail Articles +// @Description API for Save Thumbnail of Articles +// @Tags Articles +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param files formData file true "Upload thumbnail" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/thumbnail/{id} [post] +func (_i *articlesController) SaveThumbnail(c *fiber.Ctx) error { + // Get from context + err := _i.articlesService.SaveThumbnail(c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of Articles successfully created"}, + }) +} + +// Update Articles +// @Summary Update Articles +// @Description API for update Articles +// @Tags Articles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.ArticlesUpdateRequest true "Required payload" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id} [put] +func (_i *articlesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ArticlesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get from context + err = _i.articlesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully updated"}, + }) +} + +// UpdateBanner Articles +// @Summary Update Banner Articles +// @Description API for Update Banner Articles +// @Tags Articles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Articles ID" +// @Param isBanner query bool true "Articles Banner Status" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/banner/{id} [put] +func (_i *articlesController) UpdateBanner(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + isBanner, err := strconv.ParseBool(c.Query("isBanner")) + if err != nil { + return err + } + + // Get from context + err = _i.articlesService.UpdateBanner(uint(id), isBanner) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully banner updated"}, + }) +} + +// Delete Articles +// @Summary Delete Articles +// @Description API for delete Articles +// @Tags Articles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Articles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/{id} [delete] +func (_i *articlesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get from context + err = _i.articlesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Articles successfully deleted"}, + }) +} + +// Viewer Articles +// @Summary Viewer Articles Thumbnail +// @Description API for View Thumbnail of Article +// @Tags Articles +// @Security Bearer +// @Param thumbnailName path string true "Articles Thumbnail Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/thumbnail/viewer/{thumbnailName} [get] +func (_i *articlesController) Viewer(c *fiber.Ctx) error { + // Get from context + return _i.articlesService.Viewer(c) +} + +// SummaryStats Articles +// @Summary SummaryStats Articles +// @Description API for Summary Stats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/summary [get] +func (_i *articlesController) SummaryStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + // Get from context + response, err := _i.articlesService.SummaryStats(authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Summary Stats of Articles successfully retrieved"}, + Data: response, + }) +} + +// ArticlePerUserLevelStats Articles +// @Summary ArticlePerUserLevelStats Articles +// @Description API for ArticlePerUserLevelStats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param startDate query string false "start date" +// @Param endDate query string false "start date" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/user-levels [get] +func (_i *articlesController) ArticlePerUserLevelStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + startDate := c.Query("startDate") + endDate := c.Query("endDate") + + // Get from context + response, err := _i.articlesService.ArticlePerUserLevelStats(authToken, &startDate, &endDate) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticlePerUserLevelStats of Articles successfully retrieved"}, + Data: response, + }) +} + +// ArticleMonthlyStats Articles +// @Summary ArticleMonthlyStats Articles +// @Description API for ArticleMonthlyStats of Article +// @Tags Articles +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param year query int false "year" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/statistic/monthly [get] +func (_i *articlesController) ArticleMonthlyStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + year := c.Query("year") + yearInt, err := strconv.Atoi(year) + if err != nil { + return err + } + + // Get from context + response, err := _i.articlesService.ArticleMonthlyStats(authToken, &yearInt) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"ArticleMonthlyStats of Articles successfully retrieved"}, + Data: response, + }) +} + +// PublishScheduling Articles +// @Summary PublishScheduling Articles +// @Description API for Publish Schedule of Article +// @Tags Articles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id query int false "article id" +// @Param date query string false "publish date" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /articles/publish-scheduling [post] +func (_i *articlesController) PublishScheduling(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Query("id"), 10, 0) + if err != nil { + return err + } + date := c.Query("date") + + // Get from context + err = _i.articlesService.PublishScheduling(uint(id), date) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Publish Scheduling of Articles successfully saved"}, + }) +} diff --git a/app/module/articles/controller/controller.go b/app/module/articles/controller/controller.go new file mode 100644 index 0000000..3ea7124 --- /dev/null +++ b/app/module/articles/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/articles/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + Articles ArticlesController +} + +func NewController(ArticlesService service.ArticlesService, log zerolog.Logger) *Controller { + return &Controller{ + Articles: NewArticlesController(ArticlesService, log), + } +} diff --git a/app/module/articles/mapper/articles.mapper.go b/app/module/articles/mapper/articles.mapper.go new file mode 100644 index 0000000..1d8b587 --- /dev/null +++ b/app/module/articles/mapper/articles.mapper.go @@ -0,0 +1,90 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + articleCategoriesMapper "campaign-pool-be/app/module/article_categories/mapper" + articleCategoriesRepository "campaign-pool-be/app/module/article_categories/repository" + articleCategoriesResponse "campaign-pool-be/app/module/article_categories/response" + articleCategoryDetailsRepository "campaign-pool-be/app/module/article_category_details/repository" + articleFilesMapper "campaign-pool-be/app/module/article_files/mapper" + articleFilesRepository "campaign-pool-be/app/module/article_files/repository" + articleFilesResponse "campaign-pool-be/app/module/article_files/response" + res "campaign-pool-be/app/module/articles/response" + usersRepository "campaign-pool-be/app/module/users/repository" + + "github.com/rs/zerolog" +) + +func ArticlesResponseMapper( + log zerolog.Logger, + host string, + articlesReq *entity.Articles, + articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository, + articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository, + articleFilesRepo articleFilesRepository.ArticleFilesRepository, + usersRepo usersRepository.UsersRepository, +) (articlesRes *res.ArticlesResponse) { + + createdByName := "" + if articlesReq.CreatedById != nil { + findUser, _ := usersRepo.FindOne(*articlesReq.CreatedById) + if findUser != nil { + createdByName = findUser.Fullname + } + } + + categoryName := "" + articleCategories, _ := articleCategoryDetailsRepo.FindByArticleId(articlesReq.ID) + var articleCategoriesArr []*articleCategoriesResponse.ArticleCategoriesResponse + if articleCategories != nil && len(articleCategories) > 0 { + for _, result := range articleCategories { + articleCategoriesArr = append(articleCategoriesArr, articleCategoriesMapper.ArticleCategoriesResponseMapper(result.Category, host)) + } + log.Info().Interface("articleCategoriesArr", articleCategoriesArr).Msg("") + } + + articleFiles, _ := articleFilesRepo.FindByArticle(articlesReq.ID) + var articleFilesArr []*articleFilesResponse.ArticleFilesResponse + if articleFiles != nil && len(articleFiles) > 0 { + for _, result := range articleFiles { + articleFilesArr = append(articleFilesArr, articleFilesMapper.ArticleFilesResponseMapper(result, host)) + } + } + + if articlesReq != nil { + articlesRes = &res.ArticlesResponse{ + ID: articlesReq.ID, + Title: articlesReq.Title, + Slug: articlesReq.Slug, + Description: articlesReq.Description, + HtmlDescription: articlesReq.HtmlDescription, + TypeId: articlesReq.TypeId, + Tags: articlesReq.Tags, + CategoryId: articlesReq.CategoryId, + AiArticleId: articlesReq.AiArticleId, + CategoryName: categoryName, + PageUrl: articlesReq.PageUrl, + CreatedById: articlesReq.CreatedById, + CreatedByName: &createdByName, + ShareCount: articlesReq.ShareCount, + ViewCount: articlesReq.ViewCount, + CommentCount: articlesReq.CommentCount, + OldId: articlesReq.OldId, + StatusId: articlesReq.StatusId, + IsBanner: articlesReq.IsBanner, + IsPublish: articlesReq.IsPublish, + PublishedAt: articlesReq.PublishedAt, + IsActive: articlesReq.IsActive, + CreatedAt: articlesReq.CreatedAt, + UpdatedAt: articlesReq.UpdatedAt, + ArticleFiles: articleFilesArr, + ArticleCategories: articleCategoriesArr, + } + + if articlesReq.ThumbnailName != nil { + articlesRes.ThumbnailUrl = host + "/articles/thumbnail/viewer/" + *articlesReq.ThumbnailName + } + } + + return articlesRes +} diff --git a/app/module/articles/repository/articles.repository.go b/app/module/articles/repository/articles.repository.go new file mode 100644 index 0000000..baf1309 --- /dev/null +++ b/app/module/articles/repository/articles.repository.go @@ -0,0 +1,330 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/articles/request" + "campaign-pool-be/app/module/articles/response" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + "time" + + "github.com/rs/zerolog" +) + +type articlesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// ArticlesRepository define interface of IArticlesRepository +type ArticlesRepository interface { + GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) + GetAllPublishSchedule() (articless []*entity.Articles, err error) + FindOne(id uint) (articles *entity.Articles, err error) + FindByFilename(thumbnailName string) (articleReturn *entity.Articles, err error) + FindByOldId(oldId uint) (articles *entity.Articles, err error) + Create(articles *entity.Articles) (articleReturn *entity.Articles, err error) + Update(id uint, articles *entity.Articles) (err error) + UpdateSkipNull(id uint, articles *entity.Articles) (err error) + Delete(id uint) (err error) + SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) + ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) + ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) +} + +func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository { + return &articlesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IArticlesRepository +func (_i *articlesRepository) GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Articles{}) + + if req.CategoryId != nil { + query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id"). + Where("acd.category_id = ?", req.CategoryId) + } + query = query.Where("articles.is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(articles.title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(articles.description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.Tags != nil && *req.Tags != "" { + tags := strings.ToLower(*req.Tags) + query = query.Where("LOWER(articles.tags) LIKE ?", "%"+strings.ToLower(tags)+"%") + } + if req.TypeId != nil { + query = query.Where("articles.type_id = ?", req.TypeId) + } + if req.IsPublish != nil { + query = query.Where("articles.is_publish = ?", req.IsPublish) + } + if req.IsBanner != nil { + query = query.Where("articles.is_banner = ?", req.IsBanner) + } + if req.IsDraft != nil { + query = query.Where("articles.is_draft = ?", req.IsDraft) + } + if req.StatusId != nil { + query = query.Where("articles.status_id = ?", req.StatusId) + } + if req.CreatedById != nil { + query = query.Where("articles.created_by_id = ?", req.CreatedById) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "articles.created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *articlesRepository) GetAllPublishSchedule() (articles []*entity.Articles, err error) { + query := _i.DB.DB.Where("publish_schedule IS NOT NULL") + err = query.Find(&articles).Error + if err != nil { + return nil, err + } + return articles, nil +} + +func (_i *articlesRepository) FindOne(id uint) (articles *entity.Articles, err error) { + query := _i.DB.DB + if err := query.First(&articles, id).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) FindByFilename(thumbnailName string) (articles *entity.Articles, err error) { + query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName) + + if err := query.First(&articles).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) FindByOldId(oldId uint) (articles *entity.Articles, err error) { + query := _i.DB.DB.Where("old_id = ?", oldId) + + if err := query.First(&articles).Error; err != nil { + return nil, err + } + + return articles, nil +} + +func (_i *articlesRepository) Create(articles *entity.Articles) (articleReturn *entity.Articles, err error) { + result := _i.DB.DB.Create(articles) + return articles, result.Error +} + +func (_i *articlesRepository) Update(id uint, articles *entity.Articles) (err error) { + articlesMap, err := utilSvc.StructToMap(articles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.Articles{}). + Where(&entity.Articles{ID: id}). + Updates(articlesMap).Error +} + +func (_i *articlesRepository) UpdateSkipNull(id uint, articles *entity.Articles) (err error) { + return _i.DB.DB.Model(&entity.Articles{}). + Where(&entity.Articles{ID: id}). + Updates(articles).Error +} + +func (_i *articlesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Articles{}, id).Error +} + +func (_i *articlesRepository) SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) { + now := time.Now() + startOfDay := now.Truncate(24 * time.Hour) + startOfWeek := now.AddDate(0, 0, -int(now.Weekday())+1).Truncate(24 * time.Hour) + + // Query + query := _i.DB.DB.Model(&entity.Articles{}). + Select( + "COUNT(*) AS total_all, "+ + "COALESCE(SUM(view_count), 0) AS total_views, "+ + "COALESCE(SUM(share_count), 0) AS total_shares, "+ + "COALESCE(SUM(comment_count), 0) AS total_comments, "+ + "COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_today, "+ + "COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_this_week", + startOfDay, startOfWeek). + Where("created_by_id = ?", userID) + + err = query.Scan(&articleSummaryStats).Error + + return articleSummaryStats, err +} + +func (_i *articlesRepository) ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) { + + levelNumberTop := 1 + + query := _i.DB.DB.Model(&entity.Articles{}). + Select("user_levels.id as user_level_id", "user_levels.name as user_level_name", "COUNT(articles.id) as total_article"). + Joins("LEFT JOIN users ON articles.created_by_id = users.id"). + Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id"). + Where("articles.is_active = true") + + if userLevelId != nil && *levelNumber != levelNumberTop { + query = query.Where("user_levels.id = ? or user_levels.parent_level_id = ?", *userLevelId, *userLevelId) + } else { + query = _i.DB.DB.Raw(` + WITH LevelHierarchy AS ( + SELECT + id, + name, + level_number, + parent_level_id, + CASE + WHEN level_number = 1 THEN id + WHEN level_number = 2 and name ILIKE '%polda%' THEN id + WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN parent_level_id + WHEN level_number = 3 THEN parent_level_id + END AS level_2_id, + CASE + WHEN level_number = 1 THEN name + WHEN level_number = 2 and name ILIKE '%polda%' THEN name + WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id) + WHEN level_number = 3 THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id) + END AS level_2_name + FROM user_levels + ) + SELECT + lh.level_2_id AS user_level_id, + UPPER(lh.level_2_name) AS user_level_name, + COUNT(articles.id) AS total_article + FROM articles + JOIN users ON articles.created_by_id = users.id + JOIN LevelHierarchy lh ON users.user_level_id = lh.id + WHERE articles.is_active = true AND lh.level_2_id > 0`) + + query = query.Group("lh.level_2_id, lh.level_2_name").Order("total_article DESC") + } + + // Apply date filters if provided + if startDate != nil { + query = query.Where("articles.created_at >= ?", *startDate) + } + if endDate != nil { + query = query.Where("articles.created_at <= ?", *endDate) + } + + // Group by all non-aggregated columns + err = query.Group("user_levels.id, user_levels.name"). + Order("total_article DESC"). + Scan(&articlePerUserLevelStats).Error + + return articlePerUserLevelStats, err +} + +func (_i *articlesRepository) ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) { + levelNumberTop := 1 + + if year < 1900 || year > 2100 { + return nil, fmt.Errorf("invalid year") + } + + var results []struct { + Month int + Day int + TotalView int + TotalComment int + TotalShare int + } + + query := _i.DB.DB.Model(&entity.Articles{}). + Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+ + "SUM(view_count) as total_view, "+ + "SUM(comment_count) as total_comment, "+ + "SUM(share_count) as total_share"). + Where("EXTRACT(YEAR FROM created_at) = ?", year) + + if userLevelId != nil && *levelNumber != levelNumberTop { + query = _i.DB.DB.Model(&entity.Articles{}). + Select("EXTRACT(MONTH FROM articles.created_at) as month, EXTRACT(DAY FROM articles.created_at) as day, "+ + "SUM(articles.view_count) as total_view, "+ + "SUM(articles.comment_count) as total_comment, "+ + "SUM(articles.share_count) as total_share"). + Joins("LEFT JOIN users ON articles.created_by_id = users.id"). + Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id"). + Where("articles.is_active = true"). + Where("EXTRACT(YEAR FROM articles.created_at) = ?", year). + Where("(user_levels.id = ? OR user_levels.parent_level_id = ?)", *userLevelId, *userLevelId) + + } + + err = query.Group("month, day").Scan(&results).Error + if err != nil { + return nil, err + } + + // Siapkan struktur untuk menyimpan data bulanan + monthlyAnalytics := make([]*response.ArticleMonthlyStats, 12) + for i := 0; i < 12; i++ { + daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day() + monthlyAnalytics[i] = &response.ArticleMonthlyStats{ + Year: year, + Month: i + 1, + View: make([]int, daysInMonth), + Comment: make([]int, daysInMonth), + Share: make([]int, daysInMonth), + } + } + + // Isi data dari hasil agregasi + for _, result := range results { + monthIndex := result.Month - 1 + dayIndex := result.Day - 1 + + if monthIndex >= 0 && monthIndex < 12 { + if dayIndex >= 0 && dayIndex < len(monthlyAnalytics[monthIndex].View) { + monthlyAnalytics[monthIndex].View[dayIndex] = result.TotalView + monthlyAnalytics[monthIndex].Comment[dayIndex] = result.TotalComment + monthlyAnalytics[monthIndex].Share[dayIndex] = result.TotalShare + } + } + } + + return monthlyAnalytics, nil +} diff --git a/app/module/articles/request/articles.request.go b/app/module/articles/request/articles.request.go new file mode 100644 index 0000000..97a0434 --- /dev/null +++ b/app/module/articles/request/articles.request.go @@ -0,0 +1,183 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type ArticlesGeneric interface { + ToEntity() +} + +type ArticlesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + CategoryId *uint `json:"categoryId"` + Category *string `json:"category"` + TypeId *int `json:"typeId"` + Tags *string `json:"tags"` + CreatedById *int `json:"createdById"` + StatusId *int `json:"statusId"` + IsBanner *bool `json:"isBanner"` + IsPublish *bool `json:"isPublish"` + IsDraft *bool `json:"isDraft"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ArticlesCreateRequest struct { + Title string `json:"title" validate:"required"` + Slug string `json:"slug" validate:"required"` + Description string `json:"description" validate:"required"` + HtmlDescription string `json:"htmlDescription" validate:"required"` + CategoryIds string `json:"categoryIds" validate:"required"` + TypeId int `json:"typeId" validate:"required"` + Tags string `json:"tags" validate:"required"` + AiArticleId *int `json:"aiArticleId"` + CreatedAt *string `json:"createdAt"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + IsDraft *bool `json:"isDraft"` + OldId *uint `json:"oldId"` +} + +func (req ArticlesCreateRequest) ToEntity() *entity.Articles { + return &entity.Articles{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + TypeId: req.TypeId, + Tags: req.Tags, + AiArticleId: req.AiArticleId, + IsPublish: req.IsPublish, + IsDraft: req.IsDraft, + OldId: req.OldId, + } +} + +type ArticlesUpdateRequest struct { + Title string `json:"title" validate:"required"` + Slug string `json:"slug" validate:"required"` + Description string `json:"description" validate:"required"` + HtmlDescription string `json:"htmlDescription" validate:"required"` + CategoryIds string `json:"categoryIds" validate:"required"` + TypeId int `json:"typeId" validate:"required"` + Tags string `json:"tags" validate:"required"` + CreatedAt *string `json:"createdAt"` + CreatedById *uint `json:"createdById"` + AiArticleId *int `json:"aiArticleId"` + IsPublish *bool `json:"isPublish"` + IsDraft *bool `json:"isDraft"` + StatusId *int `json:"statusId"` +} + +func (req ArticlesUpdateRequest) ToEntity() *entity.Articles { + if req.CreatedById == nil { + return &entity.Articles{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + TypeId: req.TypeId, + Tags: req.Tags, + StatusId: req.StatusId, + AiArticleId: req.AiArticleId, + IsPublish: req.IsPublish, + IsDraft: req.IsDraft, + UpdatedAt: time.Now(), + } + } else { + return &entity.Articles{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + HtmlDescription: req.HtmlDescription, + TypeId: req.TypeId, + Tags: req.Tags, + StatusId: req.StatusId, + CreatedById: req.CreatedById, + AiArticleId: req.AiArticleId, + IsPublish: req.IsPublish, + IsDraft: req.IsDraft, + UpdatedAt: time.Now(), + } + } +} + +type ArticlesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + CategoryId string `json:"categoryId"` + Category string `json:"category"` + TypeId string `json:"typeId"` + Tags string `json:"tags"` + CreatedById string `json:"createdById"` + IsBanner string `json:"isBanner"` + IsPublish string `json:"isPublish"` + IsDraft string `json:"isDraft"` + StatusId string `json:"statusId"` +} + +func (req ArticlesQueryRequestContext) ToParamRequest() ArticlesQueryRequest { + var request ArticlesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if category := req.Category; category != "" { + request.Category = &category + } + if categoryIdStr := req.CategoryId; categoryIdStr != "" { + categoryId, err := strconv.Atoi(categoryIdStr) + if err == nil { + categoryIdUint := uint(categoryId) + request.CategoryId = &categoryIdUint + } + } + if typeIdStr := req.TypeId; typeIdStr != "" { + typeId, err := strconv.Atoi(typeIdStr) + if err == nil { + request.TypeId = &typeId + } + } + if tags := req.Tags; tags != "" { + request.Tags = &tags + } + if isPublishStr := req.IsPublish; isPublishStr != "" { + isPublish, err := strconv.ParseBool(isPublishStr) + if err == nil { + request.IsPublish = &isPublish + } + } + if isBannerStr := req.IsBanner; isBannerStr != "" { + isBanner, err := strconv.ParseBool(isBannerStr) + if err == nil { + request.IsBanner = &isBanner + } + } + if isDraftStr := req.IsDraft; isDraftStr != "" { + isDraft, err := strconv.ParseBool(isDraftStr) + if err == nil { + request.IsDraft = &isDraft + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + if createdByIdStr := req.CreatedById; createdByIdStr != "" { + createdById, err := strconv.Atoi(createdByIdStr) + if err == nil { + request.CreatedById = &createdById + } + } + + return request +} diff --git a/app/module/articles/response/articles.response.go b/app/module/articles/response/articles.response.go new file mode 100644 index 0000000..b1fdf89 --- /dev/null +++ b/app/module/articles/response/articles.response.go @@ -0,0 +1,61 @@ +package response + +import ( + articleCategoriesResponse "campaign-pool-be/app/module/article_categories/response" + articleFilesResponse "campaign-pool-be/app/module/article_files/response" + "time" +) + +type ArticlesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` + HtmlDescription string `json:"htmlDescription"` + CategoryId int `json:"categoryId"` + CategoryName string `json:"categoryName"` + TypeId int `json:"typeId"` + Tags string `json:"tags"` + ThumbnailUrl string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + CreatedByName *string `json:"createdByName"` + ShareCount *int `json:"shareCount"` + ViewCount *int `json:"viewCount"` + CommentCount *int `json:"commentCount"` + AiArticleId *int `json:"aiArticleId"` + OldId *uint `json:"oldId"` + StatusId *int `json:"statusId"` + IsBanner *bool `json:"isBanner"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + ArticleFiles []*articleFilesResponse.ArticleFilesResponse `json:"files"` + ArticleCategories []*articleCategoriesResponse.ArticleCategoriesResponse `json:"categories"` +} + +type ArticleSummaryStats struct { + TotalToday int `json:"totalToday"` + TotalThisWeek int `json:"totalThisWeek"` + TotalAll int `json:"totalAll"` + TotalViews int `json:"totalViews"` + TotalShares int `json:"totalShares"` + TotalComments int `json:"totalComments"` +} + +type ArticlePerUserLevelStats struct { + UserLevelID uint `json:"userLevelId"` + UserLevelName string `json:"userLevelName"` + TotalArticle int64 `json:"totalArticle"` +} + +type ArticleMonthlyStats struct { + Year int `json:"year"` + Month int `json:"month"` + View []int `json:"view"` + Comment []int `json:"comment"` + Share []int `json:"share"` +} diff --git a/app/module/articles/service/articles.service.go b/app/module/articles/service/articles.service.go new file mode 100644 index 0000000..ac4dd53 --- /dev/null +++ b/app/module/articles/service/articles.service.go @@ -0,0 +1,619 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + articleApprovalsRepository "campaign-pool-be/app/module/article_approvals/repository" + articleCategoriesRepository "campaign-pool-be/app/module/article_categories/repository" + articleCategoryDetailsRepository "campaign-pool-be/app/module/article_category_details/repository" + articleCategoryDetailsReq "campaign-pool-be/app/module/article_category_details/request" + articleFilesRepository "campaign-pool-be/app/module/article_files/repository" + "campaign-pool-be/app/module/articles/mapper" + "campaign-pool-be/app/module/articles/repository" + "campaign-pool-be/app/module/articles/request" + "campaign-pool-be/app/module/articles/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "context" + "errors" + "fmt" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// ArticlesService +type articlesService struct { + Repo repository.ArticlesRepository + ArticleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository + ArticleFilesRepo articleFilesRepository.ArticleFilesRepository + ArticleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository + ArticleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository + Log zerolog.Logger + Cfg *config.Config + UsersRepo usersRepository.UsersRepository + MinioStorage *minioStorage.MinioStorage +} + +// ArticlesService define interface of IArticlesService +type ArticlesService interface { + All(req request.ArticlesQueryRequest) (articles []*response.ArticlesResponse, paging paginator.Pagination, err error) + Show(id uint) (articles *response.ArticlesResponse, err error) + ShowByOldId(oldId uint) (articles *response.ArticlesResponse, err error) + Save(req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error) + SaveThumbnail(c *fiber.Ctx) (err error) + Update(id uint, req request.ArticlesUpdateRequest) (err error) + Delete(id uint) error + UpdateActivityCount(id uint, activityTypeId int) (err error) + UpdateApproval(id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) + UpdateBanner(id uint, isBanner bool) error + Viewer(c *fiber.Ctx) error + SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) + ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) + ArticleMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) + PublishScheduling(id uint, publishSchedule string) error +} + +// NewArticlesService init ArticlesService +func NewArticlesService( + repo repository.ArticlesRepository, + articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository, + articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository, + articleFilesRepo articleFilesRepository.ArticleFilesRepository, + articleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository, + log zerolog.Logger, + cfg *config.Config, + usersRepo usersRepository.UsersRepository, + minioStorage *minioStorage.MinioStorage) ArticlesService { + + return &articlesService{ + Repo: repo, + ArticleCategoriesRepo: articleCategoriesRepo, + ArticleCategoryDetailsRepo: articleCategoryDetailsRepo, + ArticleFilesRepo: articleFilesRepo, + ArticleApprovalsRepo: articleApprovalsRepo, + Log: log, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Cfg: cfg, + } +} + +// All implement interface of ArticlesService +func (_i *articlesService) All(req request.ArticlesQueryRequest) (articless []*response.ArticlesResponse, paging paginator.Pagination, err error) { + if req.Category != nil { + findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(*req.Category) + if err != nil { + return nil, paging, err + } + req.CategoryId = &findCategory.ID + } + + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:All"). + Interface("results", results).Msg("") + + host := _i.Cfg.App.Domain + + for _, result := range results { + articleRes := mapper.ArticlesResponseMapper(_i.Log, host, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo) + articless = append(articless, articleRes) + } + + return +} + +func (_i *articlesService) Show(id uint) (articles *response.ArticlesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticlesResponseMapper(_i.Log, host, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil +} + +func (_i *articlesService) ShowByOldId(oldId uint) (articles *response.ArticlesResponse, err error) { + result, err := _i.Repo.FindByOldId(oldId) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + + return mapper.ArticlesResponseMapper(_i.Log, host, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil +} + +func (_i *articlesService) Save(req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + var userLevelNumber int + var userParentLevelId int + if req.CreatedById != nil { + createdBy, err := _i.UsersRepo.FindOne(*req.CreatedById) + if err != nil { + return nil, fmt.Errorf("User not found") + } + newReq.CreatedById = &createdBy.ID + userLevelNumber = createdBy.UserLevel.LevelNumber + if createdBy.UserLevel.ParentLevelId != nil { + userParentLevelId = *createdBy.UserLevel.ParentLevelId + } + } else { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + userLevelNumber = createdBy.UserLevel.LevelNumber + if createdBy.UserLevel.ParentLevelId != nil { + userParentLevelId = *createdBy.UserLevel.ParentLevelId + } + } + + isDraft := true + if req.IsDraft == &isDraft { + draftedAt := time.Now() + newReq.IsDraft = &isDraft + newReq.DraftedAt = &draftedAt + isPublishFalse := false + newReq.IsPublish = &isPublishFalse + newReq.PublishedAt = nil + } + + isPublish := true + if req.IsPublish == &isPublish { + publishedAt := time.Now() + newReq.IsPublish = &isPublish + newReq.PublishedAt = &publishedAt + isDraftFalse := false + newReq.IsDraft = &isDraftFalse + newReq.DraftedAt = nil + } + + if req.CreatedAt != nil { + layout := "2006-01-02 15:04:05" + parsedTime, err := time.Parse(layout, *req.CreatedAt) + if err != nil { + return nil, fmt.Errorf("Error parsing time:", err) + } + newReq.CreatedAt = parsedTime + } + + // Approval + statusIdOne := 1 + statusIdTwo := 2 + isPublishFalse := false + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false { + newReq.NeedApprovalFrom = nil + newReq.StatusId = &statusIdTwo + } else { + newReq.NeedApprovalFrom = &userParentLevelId + newReq.StatusId = &statusIdOne + newReq.IsPublish = &isPublishFalse + newReq.PublishedAt = nil + } + + saveArticleRes, err := _i.Repo.Create(newReq) + if err != nil { + return nil, err + } + + // Approval + var articleApproval *entity.ArticleApprovals + + if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true { + articleApproval = &entity.ArticleApprovals{ + ArticleId: saveArticleRes.ID, + ApprovalBy: *newReq.CreatedById, + StatusId: statusIdOne, + Message: "Need Approval", + ApprovalAtLevel: &userLevelNumber, + } + } else { + articleApproval = &entity.ArticleApprovals{ + ArticleId: saveArticleRes.ID, + ApprovalBy: *newReq.CreatedById, + StatusId: statusIdTwo, + Message: "Publish Otomatis", + ApprovalAtLevel: nil, + } + } + + _, err = _i.ArticleApprovalsRepo.Create(articleApproval) + if err != nil { + return nil, err + } + + var categoryIds []string + if req.CategoryIds != "" { + categoryIds = strings.Split(req.CategoryIds, ",") + } + + _i.Log.Info().Interface("categoryIds", categoryIds).Msg("") + + for _, categoryId := range categoryIds { + categoryIdInt, _ := strconv.Atoi(categoryId) + + _i.Log.Info().Interface("categoryIdUint", uint(categoryIdInt)).Msg("") + + findCategory, err := _i.ArticleCategoriesRepo.FindOne(uint(categoryIdInt)) + + _i.Log.Info().Interface("findCategory", findCategory).Msg("") + + if err != nil { + return nil, err + } + + if findCategory == nil { + return nil, errors.New("category not found") + } + + categoryReq := articleCategoryDetailsReq.ArticleCategoryDetailsCreateRequest{ + ArticleId: saveArticleRes.ID, + CategoryId: categoryIdInt, + IsActive: true, + } + newCategoryReq := categoryReq.ToEntity() + + err = _i.ArticleCategoryDetailsRepo.Create(newCategoryReq) + if err != nil { + return nil, err + } + } + + return saveArticleRes, nil +} + +func (_i *articlesService) SaveThumbnail(c *fiber.Ctx) (err error) { + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:articlesService", "Methods:SaveThumbnail"). + Interface("id", id).Msg("") + + 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) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(file.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := fmt.Sprintf("articles/thumbnail/%d/%d/%s", now.Year(), now.Month(), newFilename) + + findCategory, err := _i.Repo.FindOne(uint(id)) + findCategory.ThumbnailName = &newFilename + 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 *articlesService) Update(id uint, req request.ArticlesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if req.CreatedAt != nil { + layout := "2006-01-02 15:04:05" + parsedTime, err := time.Parse(layout, *req.CreatedAt) + if err != nil { + return fmt.Errorf("Error parsing time:", err) + } + newReq.CreatedAt = parsedTime + } + + return _i.Repo.UpdateSkipNull(id, newReq) +} + +func (_i *articlesService) 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 *articlesService) Viewer(c *fiber.Ctx) (err error) { + thumbnailName := c.Params("thumbnailName") + + emptyImage := "empty-image.jpg" + searchThumbnail := emptyImage + if thumbnailName != emptyImage { + result, err := _i.Repo.FindByFilename(thumbnailName) + if err != nil { + return err + } + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer"). + Interface("resultThumbnail", result.ThumbnailPath).Msg("") + + if result.ThumbnailPath != nil { + searchThumbnail = *result.ThumbnailPath + } else { + searchThumbnail = "articles/thumbnail/" + emptyImage + } + } else { + searchThumbnail = "articles/thumbnail/" + emptyImage + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer"). + Interface("searchThumbnail", searchThumbnail).Msg("") + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := searchThumbnail + + // 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 (_i *articlesService) UpdateActivityCount(id uint, activityTypeId int) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + viewCount := 0 + if result.ViewCount != nil { + viewCount = *result.ViewCount + } + shareCount := 0 + if result.ShareCount != nil { + shareCount = *result.ShareCount + } + commentCount := 0 + if result.CommentCount != nil { + commentCount = *result.CommentCount + } + + if activityTypeId == 2 { + viewCount++ + } else if activityTypeId == 3 { + shareCount++ + } else if activityTypeId == 4 { + commentCount++ + } + result.ViewCount = &viewCount + result.ShareCount = &shareCount + result.CommentCount = &commentCount + return _i.Repo.Update(id, result) +} + +func (_i *articlesService) SummaryStats(authToken string) (summaryStats *response.ArticleSummaryStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + result, err := _i.Repo.SummaryStats(user.ID) + if err != nil { + return nil, err + } + return result, nil +} + +func (_i *articlesService) ArticlePerUserLevelStats(authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("startDate", startDate).Msg("") + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("endDate", endDate).Msg("") + + var userLevelId *uint + var userLevelNumber *int + + if user != nil { + userLevelId = &user.UserLevelId + userLevelNumber = &user.UserLevel.LevelNumber + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("userLevelId", userLevelId).Msg("") + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats"). + Interface("userLevelNumber", userLevelNumber).Msg("") + + result, err := _i.Repo.ArticlePerUserLevelStats(userLevelId, userLevelNumber, nil, nil) + if err != nil { + return nil, err + } + return result, nil +} + +func (_i *articlesService) ArticleMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + var userLevelId *uint + var userLevelNumber *int + + if user != nil { + userLevelId = &user.UserLevelId + userLevelNumber = &user.UserLevel.LevelNumber + } + + result, err := _i.Repo.ArticleMonthlyStats(userLevelId, userLevelNumber, *year) + if err != nil { + return nil, err + } + return result, nil +} + +func (_i *articlesService) UpdateApproval(id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + _i.Log.Info().Interface("statusId", statusId).Msg("") + + statusIdOne := 1 + statusIdTwo := 2 + statusIdThree := 3 + isPublish := true + isDraftFalse := false + + if statusId == 2 { + if userLevelNumber == 2 || userLevelNumber == 3 { + result.NeedApprovalFrom = &userParentLevelId + result.StatusId = &statusIdOne + } else { + result.NeedApprovalFrom = nil + result.StatusId = &statusIdTwo + + result.IsPublish = &isPublish + publishedAt := time.Now() + result.PublishedAt = &publishedAt + + result.IsDraft = &isDraftFalse + result.DraftedAt = nil + } + + userLevelIdStr := strconv.Itoa(userLevelId) + if result.HasApprovedBy == nil { + result.HasApprovedBy = &userLevelIdStr + } else { + hasApprovedBySlice := strings.Split(*result.HasApprovedBy, ",") + hasApprovedBySlice = append(hasApprovedBySlice, userLevelIdStr) + hasApprovedByJoin := strings.Join(hasApprovedBySlice, ",") + result.HasApprovedBy = &hasApprovedByJoin + } + } else if statusId == 3 { + result.StatusId = &statusIdThree + result.NeedApprovalFrom = nil + result.HasApprovedBy = nil + } + + err = _i.Repo.Update(id, result) + if err != nil { + return err + } + + return +} + +func (_i *articlesService) PublishScheduling(id uint, publishSchedule string) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + result.PublishSchedule = &publishSchedule + return _i.Repo.Update(id, result) +} + +func (_i *articlesService) UpdateBanner(id uint, isBanner bool) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + result.IsBanner = &isBanner + return _i.Repo.Update(id, result) +} + +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] +} diff --git a/app/module/campaign_destinations/campaign_destinations.module.go b/app/module/campaign_destinations/campaign_destinations.module.go new file mode 100644 index 0000000..5fa9ab3 --- /dev/null +++ b/app/module/campaign_destinations/campaign_destinations.module.go @@ -0,0 +1,55 @@ +package campaign_destinations + +import ( + "campaign-pool-be/app/module/campaign_destinations/controller" + "campaign-pool-be/app/module/campaign_destinations/repository" + "campaign-pool-be/app/module/campaign_destinations/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// CampaignDestinationsRouter struct of CampaignDestinationsRouter +type CampaignDestinationsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewCampaignDestinationsModule register bulky of CampaignDestinations module +var NewCampaignDestinationsModule = fx.Options( + // register repository of CampaignDestinations module + fx.Provide(repository.NewCampaignDestinationsRepository), + + // register service of CampaignDestinations module + fx.Provide(service.NewCampaignDestinationsService), + + // register controller of CampaignDestinations module + fx.Provide(controller.NewController), + + // register router of CampaignDestinations module + fx.Provide(NewCampaignDestinationsRouter), +) + +// NewCampaignDestinationsRouter init CampaignDestinationsRouter +func NewCampaignDestinationsRouter(fiber *fiber.App, controller *controller.Controller) *CampaignDestinationsRouter { + return &CampaignDestinationsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterCampaignDestinationsRoutes register routes of CampaignDestinations module +func (_i *CampaignDestinationsRouter) RegisterCampaignDestinationsRoutes() { + // define controllers + campaignDestinationsController := _i.Controller.CampaignDestinations + + // define routes + _i.App.Route("/campaign-destinations", func(router fiber.Router) { + router.Get("/", campaignDestinationsController.All) + router.Get("/:id", campaignDestinationsController.Show) + router.Post("/", campaignDestinationsController.Save) + router.Put("/:id", campaignDestinationsController.Update) + router.Delete("/:id", campaignDestinationsController.Delete) + }) +} + diff --git a/app/module/campaign_destinations/controller/campaign_destinations.controller.go b/app/module/campaign_destinations/controller/campaign_destinations.controller.go new file mode 100644 index 0000000..f06a724 --- /dev/null +++ b/app/module/campaign_destinations/controller/campaign_destinations.controller.go @@ -0,0 +1,202 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaign_destinations/request" + "campaign-pool-be/app/module/campaign_destinations/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type campaignDestinationsController struct { + campaignDestinationsService service.CampaignDestinationsService + Log zerolog.Logger +} + +type CampaignDestinationsController 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 NewCampaignDestinationsController(campaignDestinationsService service.CampaignDestinationsService, log zerolog.Logger) CampaignDestinationsController { + return &campaignDestinationsController{ + campaignDestinationsService: campaignDestinationsService, + Log: log, + } +} + +// All CampaignDestinations +// @Summary Get all CampaignDestinations +// @Description API for getting all CampaignDestinations +// @Tags CampaignDestinations +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.CampaignDestinationsQueryRequest 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 /campaign-destinations [get] +func (_i *campaignDestinationsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.CampaignDestinationsQueryRequestContext{ + Name: c.Query("name"), + CampaignTypeID: c.Query("campaignTypeId"), + IsActive: c.Query("isActive"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + authToken := c.Get("Authorization") + campaignDestinationsData, paging, err := _i.campaignDestinationsService.All(req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignDestinations list successfully retrieved"}, + Data: campaignDestinationsData, + Meta: paging, + }) +} + +// Show CampaignDestinations +// @Summary Get one CampaignDestinations +// @Description API for getting one CampaignDestinations +// @Tags CampaignDestinations +// @Security Bearer +// @Param id path int true "CampaignDestinations ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-destinations/{id} [get] +func (_i *campaignDestinationsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + campaignDestinationsData, err := _i.campaignDestinationsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignDestinations successfully retrieved"}, + Data: campaignDestinationsData, + }) +} + +// Save CampaignDestinations +// @Summary Create CampaignDestinations +// @Description API for create CampaignDestinations +// @Tags CampaignDestinations +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.CampaignDestinationsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-destinations [post] +func (_i *campaignDestinationsController) Save(c *fiber.Ctx) error { + req := new(request.CampaignDestinationsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.campaignDestinationsService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignDestinations successfully created"}, + Data: dataResult, + }) +} + +// Update CampaignDestinations +// @Summary Update CampaignDestinations +// @Description API for update CampaignDestinations +// @Tags CampaignDestinations +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.CampaignDestinationsUpdateRequest true "Required payload" +// @Param id path int true "CampaignDestinations ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-destinations/{id} [put] +func (_i *campaignDestinationsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CampaignDestinationsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.campaignDestinationsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignDestinations successfully updated"}, + }) +} + +// Delete CampaignDestinations +// @Summary Delete CampaignDestinations +// @Description API for delete CampaignDestinations +// @Tags CampaignDestinations +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "CampaignDestinations ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-destinations/{id} [delete] +func (_i *campaignDestinationsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.campaignDestinationsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignDestinations successfully deleted"}, + }) +} + diff --git a/app/module/campaign_destinations/controller/controller.go b/app/module/campaign_destinations/controller/controller.go new file mode 100644 index 0000000..ac45a57 --- /dev/null +++ b/app/module/campaign_destinations/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaign_destinations/service" + "github.com/rs/zerolog" +) + +type Controller struct { + CampaignDestinations CampaignDestinationsController +} + +func NewController(CampaignDestinationsService service.CampaignDestinationsService, log zerolog.Logger) *Controller { + return &Controller{ + CampaignDestinations: NewCampaignDestinationsController(CampaignDestinationsService, log), + } +} + diff --git a/app/module/campaign_destinations/mapper/campaign_destinations.mapper.go b/app/module/campaign_destinations/mapper/campaign_destinations.mapper.go new file mode 100644 index 0000000..9d185f1 --- /dev/null +++ b/app/module/campaign_destinations/mapper/campaign_destinations.mapper.go @@ -0,0 +1,32 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/campaign_destinations/response" +) + +func CampaignDestinationsResponseMapper(campaignDestinationsReq *entity.CampaignDestinations) (campaignDestinationsRes *res.CampaignDestinationsResponse) { + if campaignDestinationsReq != nil { + campaignDestinationsRes = &res.CampaignDestinationsResponse{ + ID: campaignDestinationsReq.ID, + CampaignTypeID: campaignDestinationsReq.CampaignTypeID, + SubType: campaignDestinationsReq.SubType, + Name: campaignDestinationsReq.Name, + Description: campaignDestinationsReq.Description, + URL: campaignDestinationsReq.URL, + IsActive: campaignDestinationsReq.IsActive, + CreatedAt: campaignDestinationsReq.CreatedAt, + UpdatedAt: campaignDestinationsReq.UpdatedAt, + } + + if campaignDestinationsReq.CampaignType.ID > 0 { + campaignDestinationsRes.CampaignType = &res.CampaignTypeInfo{ + ID: campaignDestinationsReq.CampaignType.ID, + Name: campaignDestinationsReq.CampaignType.Name, + Description: campaignDestinationsReq.CampaignType.Description, + } + } + } + return campaignDestinationsRes +} + diff --git a/app/module/campaign_destinations/repository/campaign_destinations.repository.go b/app/module/campaign_destinations/repository/campaign_destinations.repository.go new file mode 100644 index 0000000..213113d --- /dev/null +++ b/app/module/campaign_destinations/repository/campaign_destinations.repository.go @@ -0,0 +1,106 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaign_destinations/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type campaignDestinationsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// CampaignDestinationsRepository define interface of ICampaignDestinationsRepository +type CampaignDestinationsRepository interface { + GetAll(req request.CampaignDestinationsQueryRequest) (campaignDestinationss []*entity.CampaignDestinations, paging paginator.Pagination, err error) + FindOne(id uint) (campaignDestinations *entity.CampaignDestinations, err error) + Create(campaignDestinations *entity.CampaignDestinations) (campaignDestinationsReturn *entity.CampaignDestinations, err error) + Update(id uint, campaignDestinations *entity.CampaignDestinations) (err error) + Delete(id uint) (err error) +} + +func NewCampaignDestinationsRepository(db *database.Database, log zerolog.Logger) CampaignDestinationsRepository { + return &campaignDestinationsRepository{ + DB: db, + Log: log, + } +} + +// implement interface of ICampaignDestinationsRepository +func (_i *campaignDestinationsRepository) GetAll(req request.CampaignDestinationsQueryRequest) (campaignDestinationss []*entity.CampaignDestinations, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.CampaignDestinations{}).Preload("CampaignType") + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.CampaignTypeID != nil { + query = query.Where("campaign_type_id = ?", req.CampaignTypeID) + } + if req.IsActive != nil { + query = query.Where("is_active = ?", req.IsActive) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&campaignDestinationss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *campaignDestinationsRepository) FindOne(id uint) (campaignDestinations *entity.CampaignDestinations, err error) { + query := _i.DB.DB.Where("id = ?", id).Preload("CampaignType") + + if err := query.First(&campaignDestinations).Error; err != nil { + return nil, err + } + + return campaignDestinations, nil +} + +func (_i *campaignDestinationsRepository) Create(campaignDestinations *entity.CampaignDestinations) (campaignDestinationsReturn *entity.CampaignDestinations, err error) { + result := _i.DB.DB.Create(campaignDestinations) + return campaignDestinations, result.Error +} + +func (_i *campaignDestinationsRepository) Update(id uint, campaignDestinations *entity.CampaignDestinations) (err error) { + campaignDestinationsMap, err := utilSvc.StructToMap(campaignDestinations) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.CampaignDestinations{}).Where(&entity.CampaignDestinations{ID: id}) + return query.Updates(campaignDestinationsMap).Error +} + +func (_i *campaignDestinationsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.CampaignDestinations{}, id).Error +} + diff --git a/app/module/campaign_destinations/request/campaign_destinations.request.go b/app/module/campaign_destinations/request/campaign_destinations.request.go new file mode 100644 index 0000000..8b1b6fd --- /dev/null +++ b/app/module/campaign_destinations/request/campaign_destinations.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type CampaignDestinationsQueryRequest struct { + Name *string `json:"name"` + CampaignTypeID *uint `json:"campaignTypeId"` + IsActive *bool `json:"isActive"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CampaignDestinationsCreateRequest struct { + CampaignTypeID uint `json:"campaignTypeId" validate:"required"` + Name string `json:"name" validate:"required"` + SubType *string `json:"subType"` + Description *string `json:"description"` + URL *string `json:"url"` + IsActive *bool `json:"isActive"` + CreatedById *uint `json:"createdById"` +} + +func (req CampaignDestinationsCreateRequest) ToEntity() *entity.CampaignDestinations { + isActive := true + if req.IsActive != nil { + isActive = *req.IsActive + } + return &entity.CampaignDestinations{ + CampaignTypeID: req.CampaignTypeID, + SubType: req.SubType, + Name: req.Name, + Description: req.Description, + URL: req.URL, + IsActive: &isActive, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +type CampaignDestinationsUpdateRequest struct { + CampaignTypeID uint `json:"campaignTypeId" validate:"required"` + Name string `json:"name" validate:"required"` + SubType *string `json:"subType"` + Description *string `json:"description"` + URL *string `json:"url"` + IsActive *bool `json:"isActive"` +} + +func (req CampaignDestinationsUpdateRequest) ToEntity() *entity.CampaignDestinations { + return &entity.CampaignDestinations{ + CampaignTypeID: req.CampaignTypeID, + SubType: req.SubType, + Name: req.Name, + Description: req.Description, + URL: req.URL, + IsActive: req.IsActive, + UpdatedAt: time.Now(), + } +} + +type CampaignDestinationsQueryRequestContext struct { + Name string `json:"name"` + CampaignTypeID string `json:"campaignTypeId"` + IsActive string `json:"isActive"` +} + +func (req CampaignDestinationsQueryRequestContext) ToParamRequest() CampaignDestinationsQueryRequest { + var request CampaignDestinationsQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if campaignTypeIDStr := req.CampaignTypeID; campaignTypeIDStr != "" { + campaignTypeID, err := strconv.ParseUint(campaignTypeIDStr, 10, 32) + if err == nil { + campaignTypeIDUint := uint(campaignTypeID) + request.CampaignTypeID = &campaignTypeIDUint + } + } + if isActiveStr := req.IsActive; isActiveStr != "" { + isActive, err := strconv.ParseBool(isActiveStr) + if err == nil { + request.IsActive = &isActive + } + } + + return request +} diff --git a/app/module/campaign_destinations/response/campaign_destinations.response.go b/app/module/campaign_destinations/response/campaign_destinations.response.go new file mode 100644 index 0000000..8dce08d --- /dev/null +++ b/app/module/campaign_destinations/response/campaign_destinations.response.go @@ -0,0 +1,23 @@ +package response + +import "time" + +type CampaignDestinationsResponse struct { + ID uint `json:"id"` + CampaignTypeID uint `json:"campaignTypeId"` + CampaignType *CampaignTypeInfo `json:"campaignType,omitempty"` + SubType *string `json:"subType"` + Name string `json:"name"` + Description *string `json:"description"` + URL *string `json:"url"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type CampaignTypeInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` +} + diff --git a/app/module/campaign_destinations/service/campaign_destinations.service.go b/app/module/campaign_destinations/service/campaign_destinations.service.go new file mode 100644 index 0000000..7088b8a --- /dev/null +++ b/app/module/campaign_destinations/service/campaign_destinations.service.go @@ -0,0 +1,94 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaign_destinations/mapper" + "campaign-pool-be/app/module/campaign_destinations/repository" + "campaign-pool-be/app/module/campaign_destinations/request" + "campaign-pool-be/app/module/campaign_destinations/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + + "github.com/rs/zerolog" +) + +// CampaignDestinationsService +type campaignDestinationsService struct { + Repo repository.CampaignDestinationsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config +} + +// CampaignDestinationsService define interface of ICampaignDestinationsService +type CampaignDestinationsService interface { + All(req request.CampaignDestinationsQueryRequest, authToken string) (campaignDestinations []*response.CampaignDestinationsResponse, paging paginator.Pagination, err error) + Show(id uint) (campaignDestinations *response.CampaignDestinationsResponse, err error) + Save(req request.CampaignDestinationsCreateRequest, authToken string) (campaignDestinations *entity.CampaignDestinations, err error) + Update(id uint, req request.CampaignDestinationsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewCampaignDestinationsService init CampaignDestinationsService +func NewCampaignDestinationsService(repo repository.CampaignDestinationsRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) CampaignDestinationsService { + return &campaignDestinationsService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of CampaignDestinationsService +func (_i *campaignDestinationsService) All(req request.CampaignDestinationsQueryRequest, authToken string) (campaignDestinationss []*response.CampaignDestinationsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + campaignDestinationss = append(campaignDestinationss, mapper.CampaignDestinationsResponseMapper(result)) + } + + return +} + +func (_i *campaignDestinationsService) Show(id uint) (campaignDestinations *response.CampaignDestinationsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + return mapper.CampaignDestinationsResponseMapper(result), nil +} + +func (_i *campaignDestinationsService) Save(req request.CampaignDestinationsCreateRequest, authToken string) (campaignDestinations *entity.CampaignDestinations, 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 + } + _ = createdBy + } else { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + _ = createdBy + } + + return _i.Repo.Create(newReq) +} + +func (_i *campaignDestinationsService) Update(id uint, req request.CampaignDestinationsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + return _i.Repo.Update(id, newReq) +} + +func (_i *campaignDestinationsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} + diff --git a/app/module/campaign_files/campaign_files.module.go b/app/module/campaign_files/campaign_files.module.go new file mode 100644 index 0000000..3fa9167 --- /dev/null +++ b/app/module/campaign_files/campaign_files.module.go @@ -0,0 +1,56 @@ +package campaign_files + +import ( + "campaign-pool-be/app/module/campaign_files/controller" + "campaign-pool-be/app/module/campaign_files/repository" + "campaign-pool-be/app/module/campaign_files/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// CampaignFilesRouter struct of CampaignFilesRouter +type CampaignFilesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewCampaignFilesModule register bulky of CampaignFiles module +var NewCampaignFilesModule = fx.Options( + // register repository of CampaignFiles module + fx.Provide(repository.NewCampaignFilesRepository), + + // register service of CampaignFiles module + fx.Provide(service.NewCampaignFilesService), + + // register controller of CampaignFiles module + fx.Provide(controller.NewController), + + // register router of CampaignFiles module + fx.Provide(NewCampaignFilesRouter), +) + +// NewCampaignFilesRouter init CampaignFilesRouter +func NewCampaignFilesRouter(fiber *fiber.App, controller *controller.Controller) *CampaignFilesRouter { + return &CampaignFilesRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterCampaignFilesRoutes register routes of CampaignFiles module +func (_i *CampaignFilesRouter) RegisterCampaignFilesRoutes() { + // define controllers + campaignFilesController := _i.Controller.CampaignFiles + + // define routes + _i.App.Route("/campaign-files", func(router fiber.Router) { + router.Get("/", campaignFilesController.All) + router.Get("/:id", campaignFilesController.Show) + router.Get("/campaign/:campaignId", campaignFilesController.GetByCampaign) + router.Post("/", campaignFilesController.Save) + router.Put("/:id", campaignFilesController.Update) + router.Delete("/:id", campaignFilesController.Delete) + }) +} + diff --git a/app/module/campaign_files/controller/campaign_files.controller.go b/app/module/campaign_files/controller/campaign_files.controller.go new file mode 100644 index 0000000..178b97f --- /dev/null +++ b/app/module/campaign_files/controller/campaign_files.controller.go @@ -0,0 +1,157 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaign_files/request" + "campaign-pool-be/app/module/campaign_files/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type campaignFilesController struct { + campaignFilesService service.CampaignFilesService + Log zerolog.Logger +} + +type CampaignFilesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + GetByCampaign(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewCampaignFilesController(campaignFilesService service.CampaignFilesService, log zerolog.Logger) CampaignFilesController { + return &campaignFilesController{ + campaignFilesService: campaignFilesService, + Log: log, + } +} + +func (_i *campaignFilesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.CampaignFilesQueryRequestContext{ + CampaignID: c.Query("campaignId"), + Type: c.Query("type"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + authToken := c.Get("Authorization") + data, paging, err := _i.campaignFilesService.All(req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignFiles list successfully retrieved"}, + Data: data, + Meta: paging, + }) +} + +func (_i *campaignFilesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + data, err := _i.campaignFilesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignFiles successfully retrieved"}, + Data: data, + }) +} + +func (_i *campaignFilesController) GetByCampaign(c *fiber.Ctx) error { + campaignID, err := strconv.ParseUint(c.Params("campaignId"), 10, 0) + if err != nil { + return err + } + + data, err := _i.campaignFilesService.GetByCampaign(uint(campaignID)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignFiles successfully retrieved"}, + Data: data, + }) +} + +func (_i *campaignFilesController) Save(c *fiber.Ctx) error { + req := new(request.CampaignFilesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + dataResult, err := _i.campaignFilesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignFiles successfully created"}, + Data: dataResult, + }) +} + +func (_i *campaignFilesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CampaignFilesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.campaignFilesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignFiles successfully updated"}, + }) +} + +func (_i *campaignFilesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.campaignFilesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignFiles successfully deleted"}, + }) +} + diff --git a/app/module/campaign_files/controller/controller.go b/app/module/campaign_files/controller/controller.go new file mode 100644 index 0000000..b0967ce --- /dev/null +++ b/app/module/campaign_files/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaign_files/service" + "github.com/rs/zerolog" +) + +type Controller struct { + CampaignFiles CampaignFilesController +} + +func NewController(CampaignFilesService service.CampaignFilesService, log zerolog.Logger) *Controller { + return &Controller{ + CampaignFiles: NewCampaignFilesController(CampaignFilesService, log), + } +} + diff --git a/app/module/campaign_files/mapper/campaign_files.mapper.go b/app/module/campaign_files/mapper/campaign_files.mapper.go new file mode 100644 index 0000000..411c5dd --- /dev/null +++ b/app/module/campaign_files/mapper/campaign_files.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/campaign_files/response" +) + +func CampaignFilesResponseMapper(campaignFilesReq *entity.CampaignFiles) (campaignFilesRes *res.CampaignFilesResponse) { + if campaignFilesReq != nil { + campaignFilesRes = &res.CampaignFilesResponse{ + ID: campaignFilesReq.ID, + CampaignID: campaignFilesReq.CampaignID, + Type: campaignFilesReq.Type, + FileURL: campaignFilesReq.FileURL, + ExternalURL: campaignFilesReq.ExternalURL, + IsDraft: campaignFilesReq.IsDraft, + IsPublish: campaignFilesReq.IsPublish, + IsActive: campaignFilesReq.IsActive, + CreatedAt: campaignFilesReq.CreatedAt, + UpdatedAt: campaignFilesReq.UpdatedAt, + } + } + return campaignFilesRes +} + diff --git a/app/module/campaign_files/repository/campaign_files.repository.go b/app/module/campaign_files/repository/campaign_files.repository.go new file mode 100644 index 0000000..3a68b30 --- /dev/null +++ b/app/module/campaign_files/repository/campaign_files.repository.go @@ -0,0 +1,100 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaign_files/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + + "github.com/rs/zerolog" +) + +type campaignFilesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +type CampaignFilesRepository interface { + GetAll(req request.CampaignFilesQueryRequest) (campaignFiless []*entity.CampaignFiles, paging paginator.Pagination, err error) + FindOne(id uint) (campaignFiles *entity.CampaignFiles, err error) + FindByCampaign(campaignID uint) (campaignFiles []*entity.CampaignFiles, err error) + Create(campaignFiles *entity.CampaignFiles) (campaignFilesReturn *entity.CampaignFiles, err error) + Update(id uint, campaignFiles *entity.CampaignFiles) (err error) + Delete(id uint) (err error) +} + +func NewCampaignFilesRepository(db *database.Database, log zerolog.Logger) CampaignFilesRepository { + return &campaignFilesRepository{ + DB: db, + Log: log, + } +} + +func (_i *campaignFilesRepository) GetAll(req request.CampaignFilesQueryRequest) (campaignFiless []*entity.CampaignFiles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.CampaignFiles{}).Preload("Campaign") + + if req.CampaignID != nil { + query = query.Where("campaign_id = ?", req.CampaignID) + } + if req.Type != nil && *req.Type != "" { + query = query.Where("type = ?", req.Type) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + query.Order("created_at DESC") + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&campaignFiless).Error + if err != nil { + return + } + + paging = *req.Pagination + return +} + +func (_i *campaignFilesRepository) FindOne(id uint) (campaignFiles *entity.CampaignFiles, err error) { + query := _i.DB.DB.Where("id = ?", id).Preload("Campaign") + if err := query.First(&campaignFiles).Error; err != nil { + return nil, err + } + return campaignFiles, nil +} + +func (_i *campaignFilesRepository) FindByCampaign(campaignID uint) (campaignFiles []*entity.CampaignFiles, err error) { + query := _i.DB.DB.Where("campaign_id = ?", campaignID).Preload("Campaign") + err = query.Find(&campaignFiles).Error + return +} + +func (_i *campaignFilesRepository) Create(campaignFiles *entity.CampaignFiles) (campaignFilesReturn *entity.CampaignFiles, err error) { + result := _i.DB.DB.Create(campaignFiles) + return campaignFiles, result.Error +} + +func (_i *campaignFilesRepository) Update(id uint, campaignFiles *entity.CampaignFiles) (err error) { + campaignFilesMap, err := utilSvc.StructToMap(campaignFiles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.CampaignFiles{}).Where(&entity.CampaignFiles{ID: id}).Updates(campaignFilesMap).Error +} + +func (_i *campaignFilesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.CampaignFiles{}, id).Error +} + diff --git a/app/module/campaign_files/request/campaign_files.request.go b/app/module/campaign_files/request/campaign_files.request.go new file mode 100644 index 0000000..2e597a9 --- /dev/null +++ b/app/module/campaign_files/request/campaign_files.request.go @@ -0,0 +1,86 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type CampaignFilesQueryRequest struct { + CampaignID *uint `json:"campaignId"` + Type *string `json:"type"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CampaignFilesCreateRequest struct { + CampaignID uint `json:"campaignId" validate:"required"` + Type string `json:"type" validate:"required"` // url, file + FileURL *string `json:"fileUrl"` + ExternalURL *string `json:"externalUrl"` + IsDraft *bool `json:"isDraft"` + IsPublish *bool `json:"isPublish"` +} + +func (req CampaignFilesCreateRequest) ToEntity() *entity.CampaignFiles { + isDraft := false + isPublish := false + if req.IsDraft != nil { + isDraft = *req.IsDraft + } + if req.IsPublish != nil { + isPublish = *req.IsPublish + } + return &entity.CampaignFiles{ + CampaignID: req.CampaignID, + Type: req.Type, + FileURL: req.FileURL, + ExternalURL: req.ExternalURL, + IsDraft: &isDraft, + IsPublish: &isPublish, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +type CampaignFilesUpdateRequest struct { + Type string `json:"type" validate:"required"` + FileURL *string `json:"fileUrl"` + ExternalURL *string `json:"externalUrl"` + IsDraft *bool `json:"isDraft"` + IsPublish *bool `json:"isPublish"` +} + +func (req CampaignFilesUpdateRequest) ToEntity() *entity.CampaignFiles { + return &entity.CampaignFiles{ + Type: req.Type, + FileURL: req.FileURL, + ExternalURL: req.ExternalURL, + IsDraft: req.IsDraft, + IsPublish: req.IsPublish, + UpdatedAt: time.Now(), + } +} + +type CampaignFilesQueryRequestContext struct { + CampaignID string `json:"campaignId"` + Type string `json:"type"` +} + +func (req CampaignFilesQueryRequestContext) ToParamRequest() CampaignFilesQueryRequest { + var request CampaignFilesQueryRequest + + if campaignIDStr := req.CampaignID; campaignIDStr != "" { + campaignID, err := strconv.ParseUint(campaignIDStr, 10, 32) + if err == nil { + campaignIDUint := uint(campaignID) + request.CampaignID = &campaignIDUint + } + } + if typeStr := req.Type; typeStr != "" { + request.Type = &typeStr + } + + return request +} + diff --git a/app/module/campaign_files/response/campaign_files.response.go b/app/module/campaign_files/response/campaign_files.response.go new file mode 100644 index 0000000..53a541b --- /dev/null +++ b/app/module/campaign_files/response/campaign_files.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type CampaignFilesResponse struct { + ID uint `json:"id"` + CampaignID uint `json:"campaignId"` + Type string `json:"type"` + FileURL *string `json:"fileUrl"` + ExternalURL *string `json:"externalUrl"` + IsDraft *bool `json:"isDraft"` + IsPublish *bool `json:"isPublish"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + diff --git a/app/module/campaign_files/service/campaign_files.service.go b/app/module/campaign_files/service/campaign_files.service.go new file mode 100644 index 0000000..c41f93a --- /dev/null +++ b/app/module/campaign_files/service/campaign_files.service.go @@ -0,0 +1,86 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaign_files/mapper" + "campaign-pool-be/app/module/campaign_files/repository" + "campaign-pool-be/app/module/campaign_files/request" + "campaign-pool-be/app/module/campaign_files/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type campaignFilesService struct { + Repo repository.CampaignFilesRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config +} + +type CampaignFilesService interface { + All(req request.CampaignFilesQueryRequest, authToken string) (campaignFiles []*response.CampaignFilesResponse, paging paginator.Pagination, err error) + Show(id uint) (campaignFiles *response.CampaignFilesResponse, err error) + GetByCampaign(campaignID uint) (campaignFiles []*response.CampaignFilesResponse, err error) + Save(req request.CampaignFilesCreateRequest, authToken string) (campaignFiles *entity.CampaignFiles, err error) + Update(id uint, req request.CampaignFilesUpdateRequest) (err error) + Delete(id uint) error +} + +func NewCampaignFilesService(repo repository.CampaignFilesRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) CampaignFilesService { + return &campaignFilesService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + Cfg: cfg, + } +} + +func (_i *campaignFilesService) All(req request.CampaignFilesQueryRequest, authToken string) (campaignFiless []*response.CampaignFilesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + campaignFiless = append(campaignFiless, mapper.CampaignFilesResponseMapper(result)) + } + return +} + +func (_i *campaignFilesService) Show(id uint) (campaignFiles *response.CampaignFilesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + return mapper.CampaignFilesResponseMapper(result), nil +} + +func (_i *campaignFilesService) GetByCampaign(campaignID uint) (campaignFiless []*response.CampaignFilesResponse, err error) { + results, err := _i.Repo.FindByCampaign(campaignID) + if err != nil { + return nil, err + } + + for _, result := range results { + campaignFiless = append(campaignFiless, mapper.CampaignFilesResponseMapper(result)) + } + return +} + +func (_i *campaignFilesService) Save(req request.CampaignFilesCreateRequest, authToken string) (campaignFiles *entity.CampaignFiles, err error) { + newReq := req.ToEntity() + return _i.Repo.Create(newReq) +} + +func (_i *campaignFilesService) Update(id uint, req request.CampaignFilesUpdateRequest) (err error) { + newReq := req.ToEntity() + return _i.Repo.Update(id, newReq) +} + +func (_i *campaignFilesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} + diff --git a/app/module/campaign_types/campaign_types.module.go b/app/module/campaign_types/campaign_types.module.go new file mode 100644 index 0000000..a29d92d --- /dev/null +++ b/app/module/campaign_types/campaign_types.module.go @@ -0,0 +1,55 @@ +package campaign_types + +import ( + "campaign-pool-be/app/module/campaign_types/controller" + "campaign-pool-be/app/module/campaign_types/repository" + "campaign-pool-be/app/module/campaign_types/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// CampaignTypesRouter struct of CampaignTypesRouter +type CampaignTypesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewCampaignTypesModule register bulky of CampaignTypes module +var NewCampaignTypesModule = fx.Options( + // register repository of CampaignTypes module + fx.Provide(repository.NewCampaignTypesRepository), + + // register service of CampaignTypes module + fx.Provide(service.NewCampaignTypesService), + + // register controller of CampaignTypes module + fx.Provide(controller.NewController), + + // register router of CampaignTypes module + fx.Provide(NewCampaignTypesRouter), +) + +// NewCampaignTypesRouter init CampaignTypesRouter +func NewCampaignTypesRouter(fiber *fiber.App, controller *controller.Controller) *CampaignTypesRouter { + return &CampaignTypesRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterCampaignTypesRoutes register routes of CampaignTypes module +func (_i *CampaignTypesRouter) RegisterCampaignTypesRoutes() { + // define controllers + campaignTypesController := _i.Controller.CampaignTypes + + // define routes + _i.App.Route("/campaign-types", func(router fiber.Router) { + router.Get("/", campaignTypesController.All) + router.Get("/:id", campaignTypesController.Show) + router.Post("/", campaignTypesController.Save) + router.Put("/:id", campaignTypesController.Update) + router.Delete("/:id", campaignTypesController.Delete) + }) +} + diff --git a/app/module/campaign_types/controller/campaign_types.controller.go b/app/module/campaign_types/controller/campaign_types.controller.go new file mode 100644 index 0000000..6bf41eb --- /dev/null +++ b/app/module/campaign_types/controller/campaign_types.controller.go @@ -0,0 +1,200 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaign_types/request" + "campaign-pool-be/app/module/campaign_types/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type campaignTypesController struct { + campaignTypesService service.CampaignTypesService + Log zerolog.Logger +} + +type CampaignTypesController 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 NewCampaignTypesController(campaignTypesService service.CampaignTypesService, log zerolog.Logger) CampaignTypesController { + return &campaignTypesController{ + campaignTypesService: campaignTypesService, + Log: log, + } +} + +// All CampaignTypes +// @Summary Get all CampaignTypes +// @Description API for getting all CampaignTypes +// @Tags CampaignTypes +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.CampaignTypesQueryRequest 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 /campaign-types [get] +func (_i *campaignTypesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.CampaignTypesQueryRequestContext{ + Name: c.Query("name"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + authToken := c.Get("Authorization") + campaignTypesData, paging, err := _i.campaignTypesService.All(req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignTypes list successfully retrieved"}, + Data: campaignTypesData, + Meta: paging, + }) +} + +// Show CampaignTypes +// @Summary Get one CampaignTypes +// @Description API for getting one CampaignTypes +// @Tags CampaignTypes +// @Security Bearer +// @Param id path int true "CampaignTypes ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-types/{id} [get] +func (_i *campaignTypesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + campaignTypesData, err := _i.campaignTypesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignTypes successfully retrieved"}, + Data: campaignTypesData, + }) +} + +// Save CampaignTypes +// @Summary Create CampaignTypes +// @Description API for create CampaignTypes +// @Tags CampaignTypes +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.CampaignTypesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-types [post] +func (_i *campaignTypesController) Save(c *fiber.Ctx) error { + req := new(request.CampaignTypesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.campaignTypesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignTypes successfully created"}, + Data: dataResult, + }) +} + +// Update CampaignTypes +// @Summary Update CampaignTypes +// @Description API for update CampaignTypes +// @Tags CampaignTypes +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.CampaignTypesUpdateRequest true "Required payload" +// @Param id path int true "CampaignTypes ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-types/{id} [put] +func (_i *campaignTypesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CampaignTypesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.campaignTypesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignTypes successfully updated"}, + }) +} + +// Delete CampaignTypes +// @Summary Delete CampaignTypes +// @Description API for delete CampaignTypes +// @Tags CampaignTypes +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "CampaignTypes ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaign-types/{id} [delete] +func (_i *campaignTypesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.campaignTypesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CampaignTypes successfully deleted"}, + }) +} + diff --git a/app/module/campaign_types/controller/controller.go b/app/module/campaign_types/controller/controller.go new file mode 100644 index 0000000..c186355 --- /dev/null +++ b/app/module/campaign_types/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaign_types/service" + "github.com/rs/zerolog" +) + +type Controller struct { + CampaignTypes CampaignTypesController +} + +func NewController(CampaignTypesService service.CampaignTypesService, log zerolog.Logger) *Controller { + return &Controller{ + CampaignTypes: NewCampaignTypesController(CampaignTypesService, log), + } +} + diff --git a/app/module/campaign_types/mapper/campaign_types.mapper.go b/app/module/campaign_types/mapper/campaign_types.mapper.go new file mode 100644 index 0000000..63055af --- /dev/null +++ b/app/module/campaign_types/mapper/campaign_types.mapper.go @@ -0,0 +1,21 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/campaign_types/response" +) + +func CampaignTypesResponseMapper(campaignTypesReq *entity.CampaignTypes) (campaignTypesRes *res.CampaignTypesResponse) { + if campaignTypesReq != nil { + campaignTypesRes = &res.CampaignTypesResponse{ + ID: campaignTypesReq.ID, + Name: campaignTypesReq.Name, + Description: campaignTypesReq.Description, + IsActive: campaignTypesReq.IsActive, + CreatedAt: campaignTypesReq.CreatedAt, + UpdatedAt: campaignTypesReq.UpdatedAt, + } + } + return campaignTypesRes +} + diff --git a/app/module/campaign_types/repository/campaign_types.repository.go b/app/module/campaign_types/repository/campaign_types.repository.go new file mode 100644 index 0000000..4120597 --- /dev/null +++ b/app/module/campaign_types/repository/campaign_types.repository.go @@ -0,0 +1,100 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaign_types/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type campaignTypesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// CampaignTypesRepository define interface of ICampaignTypesRepository +type CampaignTypesRepository interface { + GetAll(req request.CampaignTypesQueryRequest) (campaignTypess []*entity.CampaignTypes, paging paginator.Pagination, err error) + FindOne(id uint) (campaignTypes *entity.CampaignTypes, err error) + Create(campaignTypes *entity.CampaignTypes) (campaignTypesReturn *entity.CampaignTypes, err error) + Update(id uint, campaignTypes *entity.CampaignTypes) (err error) + Delete(id uint) (err error) +} + +func NewCampaignTypesRepository(db *database.Database, log zerolog.Logger) CampaignTypesRepository { + return &campaignTypesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of ICampaignTypesRepository +func (_i *campaignTypesRepository) GetAll(req request.CampaignTypesQueryRequest) (campaignTypess []*entity.CampaignTypes, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.CampaignTypes{}) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&campaignTypess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *campaignTypesRepository) FindOne(id uint) (campaignTypes *entity.CampaignTypes, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.First(&campaignTypes).Error; err != nil { + return nil, err + } + + return campaignTypes, nil +} + +func (_i *campaignTypesRepository) Create(campaignTypes *entity.CampaignTypes) (campaignTypesReturn *entity.CampaignTypes, err error) { + result := _i.DB.DB.Create(campaignTypes) + return campaignTypes, result.Error +} + +func (_i *campaignTypesRepository) Update(id uint, campaignTypes *entity.CampaignTypes) (err error) { + campaignTypesMap, err := utilSvc.StructToMap(campaignTypes) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.CampaignTypes{}).Where(&entity.CampaignTypes{ID: id}) + return query.Updates(campaignTypesMap).Error +} + +func (_i *campaignTypesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.CampaignTypes{}, id).Error +} + diff --git a/app/module/campaign_types/request/campaign_types.request.go b/app/module/campaign_types/request/campaign_types.request.go new file mode 100644 index 0000000..e576999 --- /dev/null +++ b/app/module/campaign_types/request/campaign_types.request.go @@ -0,0 +1,55 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "time" +) + +type CampaignTypesQueryRequest struct { + Name *string `json:"name"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CampaignTypesCreateRequest struct { + Name string `json:"name" validate:"required"` + Description *string `json:"description"` + CreatedById *uint `json:"createdById"` +} + +func (req CampaignTypesCreateRequest) ToEntity() *entity.CampaignTypes { + return &entity.CampaignTypes{ + Name: req.Name, + Description: req.Description, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } +} + +type CampaignTypesUpdateRequest struct { + Name string `json:"name" validate:"required"` + Description *string `json:"description"` +} + +func (req CampaignTypesUpdateRequest) ToEntity() *entity.CampaignTypes { + return &entity.CampaignTypes{ + Name: req.Name, + Description: req.Description, + UpdatedAt: time.Now(), + } +} + +type CampaignTypesQueryRequestContext struct { + Name string `json:"name"` +} + +func (req CampaignTypesQueryRequestContext) ToParamRequest() CampaignTypesQueryRequest { + var request CampaignTypesQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + + return request +} + diff --git a/app/module/campaign_types/response/campaign_types.response.go b/app/module/campaign_types/response/campaign_types.response.go new file mode 100644 index 0000000..2addfca --- /dev/null +++ b/app/module/campaign_types/response/campaign_types.response.go @@ -0,0 +1,13 @@ +package response + +import "time" + +type CampaignTypesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + diff --git a/app/module/campaign_types/service/campaign_types.service.go b/app/module/campaign_types/service/campaign_types.service.go new file mode 100644 index 0000000..3d3970e --- /dev/null +++ b/app/module/campaign_types/service/campaign_types.service.go @@ -0,0 +1,96 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaign_types/mapper" + "campaign-pool-be/app/module/campaign_types/repository" + "campaign-pool-be/app/module/campaign_types/request" + "campaign-pool-be/app/module/campaign_types/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + + "github.com/rs/zerolog" +) + +// CampaignTypesService +type campaignTypesService struct { + Repo repository.CampaignTypesRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config +} + +// CampaignTypesService define interface of ICampaignTypesService +type CampaignTypesService interface { + All(req request.CampaignTypesQueryRequest, authToken string) (campaignTypes []*response.CampaignTypesResponse, paging paginator.Pagination, err error) + Show(id uint) (campaignTypes *response.CampaignTypesResponse, err error) + Save(req request.CampaignTypesCreateRequest, authToken string) (campaignTypes *entity.CampaignTypes, err error) + Update(id uint, req request.CampaignTypesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewCampaignTypesService init CampaignTypesService +func NewCampaignTypesService(repo repository.CampaignTypesRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) CampaignTypesService { + return &campaignTypesService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of CampaignTypesService +func (_i *campaignTypesService) All(req request.CampaignTypesQueryRequest, authToken string) (campaignTypess []*response.CampaignTypesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + campaignTypess = append(campaignTypess, mapper.CampaignTypesResponseMapper(result)) + } + + return +} + +func (_i *campaignTypesService) Show(id uint) (campaignTypes *response.CampaignTypesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + return mapper.CampaignTypesResponseMapper(result), nil +} + +func (_i *campaignTypesService) Save(req request.CampaignTypesCreateRequest, authToken string) (campaignTypes *entity.CampaignTypes, 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.CreatedAt = createdBy.CreatedAt + } else { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if createdBy != nil { + // User info available if needed + } + } + + return _i.Repo.Create(newReq) +} + +func (_i *campaignTypesService) Update(id uint, req request.CampaignTypesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + return _i.Repo.Update(id, newReq) +} + +func (_i *campaignTypesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} + diff --git a/app/module/campaigns/campaigns.module.go b/app/module/campaigns/campaigns.module.go new file mode 100644 index 0000000..29bbdf4 --- /dev/null +++ b/app/module/campaigns/campaigns.module.go @@ -0,0 +1,55 @@ +package campaigns + +import ( + "campaign-pool-be/app/module/campaigns/controller" + "campaign-pool-be/app/module/campaigns/repository" + "campaign-pool-be/app/module/campaigns/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// CampaignsRouter struct of CampaignsRouter +type CampaignsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewCampaignsModule register bulky of Campaigns module +var NewCampaignsModule = fx.Options( + // register repository of Campaigns module + fx.Provide(repository.NewCampaignsRepository), + + // register service of Campaigns module + fx.Provide(service.NewCampaignsService), + + // register controller of Campaigns module + fx.Provide(controller.NewController), + + // register router of Campaigns module + fx.Provide(NewCampaignsRouter), +) + +// NewCampaignsRouter init CampaignsRouter +func NewCampaignsRouter(fiber *fiber.App, controller *controller.Controller) *CampaignsRouter { + return &CampaignsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterCampaignsRoutes register routes of Campaigns module +func (_i *CampaignsRouter) RegisterCampaignsRoutes() { + // define controllers + campaignsController := _i.Controller.Campaigns + + // define routes + _i.App.Route("/campaigns", func(router fiber.Router) { + router.Get("/", campaignsController.All) + router.Get("/:id", campaignsController.Show) + router.Post("/", campaignsController.Save) + router.Put("/:id", campaignsController.Update) + router.Delete("/:id", campaignsController.Delete) + }) +} + diff --git a/app/module/campaigns/controller/campaigns.controller.go b/app/module/campaigns/controller/campaigns.controller.go new file mode 100644 index 0000000..5e965ab --- /dev/null +++ b/app/module/campaigns/controller/campaigns.controller.go @@ -0,0 +1,203 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaigns/request" + "campaign-pool-be/app/module/campaigns/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type campaignsController struct { + campaignsService service.CampaignsService + Log zerolog.Logger +} + +type CampaignsController 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 NewCampaignsController(campaignsService service.CampaignsService, log zerolog.Logger) CampaignsController { + return &campaignsController{ + campaignsService: campaignsService, + Log: log, + } +} + +// All Campaigns +// @Summary Get all Campaigns +// @Description API for getting all Campaigns +// @Tags Campaigns +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.CampaignsQueryRequest 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 /campaigns [get] +func (_i *campaignsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.CampaignsQueryRequestContext{ + Title: c.Query("title"), + CampaignTypeID: c.Query("campaignTypeId"), + Status: c.Query("status"), + CreatorID: c.Query("creatorId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + authToken := c.Get("Authorization") + campaignsData, paging, err := _i.campaignsService.All(req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Campaigns list successfully retrieved"}, + Data: campaignsData, + Meta: paging, + }) +} + +// Show Campaigns +// @Summary Get one Campaigns +// @Description API for getting one Campaigns +// @Tags Campaigns +// @Security Bearer +// @Param id path int true "Campaigns ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaigns/{id} [get] +func (_i *campaignsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + campaignsData, err := _i.campaignsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Campaigns successfully retrieved"}, + Data: campaignsData, + }) +} + +// Save Campaigns +// @Summary Create Campaigns +// @Description API for create Campaigns +// @Tags Campaigns +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.CampaignsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaigns [post] +func (_i *campaignsController) Save(c *fiber.Ctx) error { + req := new(request.CampaignsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.campaignsService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Campaigns successfully created"}, + Data: dataResult, + }) +} + +// Update Campaigns +// @Summary Update Campaigns +// @Description API for update Campaigns +// @Tags Campaigns +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.CampaignsUpdateRequest true "Required payload" +// @Param id path int true "Campaigns ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaigns/{id} [put] +func (_i *campaignsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CampaignsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.campaignsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Campaigns successfully updated"}, + }) +} + +// Delete Campaigns +// @Summary Delete Campaigns +// @Description API for delete Campaigns +// @Tags Campaigns +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Campaigns ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /campaigns/{id} [delete] +func (_i *campaignsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.campaignsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Campaigns successfully deleted"}, + }) +} + diff --git a/app/module/campaigns/controller/controller.go b/app/module/campaigns/controller/controller.go new file mode 100644 index 0000000..2b25c51 --- /dev/null +++ b/app/module/campaigns/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/campaigns/service" + "github.com/rs/zerolog" +) + +type Controller struct { + Campaigns CampaignsController +} + +func NewController(CampaignsService service.CampaignsService, log zerolog.Logger) *Controller { + return &Controller{ + Campaigns: NewCampaignsController(CampaignsService, log), + } +} + diff --git a/app/module/campaigns/mapper/campaigns.mapper.go b/app/module/campaigns/mapper/campaigns.mapper.go new file mode 100644 index 0000000..9c7b53e --- /dev/null +++ b/app/module/campaigns/mapper/campaigns.mapper.go @@ -0,0 +1,46 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/campaigns/response" +) + +func CampaignsResponseMapper(campaignsReq *entity.Campaigns, host string) (campaignsRes *res.CampaignsResponse) { + if campaignsReq != nil { + campaignsRes = &res.CampaignsResponse{ + ID: campaignsReq.ID, + Title: campaignsReq.Title, + CampaignTypeID: campaignsReq.CampaignTypeID, + StartDate: campaignsReq.StartDate, + EndDate: campaignsReq.EndDate, + MediaTypeSelected: campaignsReq.MediaTypeSelected, + MediaItemSelected: campaignsReq.MediaItemSelected, + Purpose: campaignsReq.Purpose, + MediaPromote: campaignsReq.MediaPromote, + Description: campaignsReq.Description, + CreatorID: campaignsReq.CreatorID, + Status: campaignsReq.Status, + IsActive: campaignsReq.IsActive, + CreatedAt: campaignsReq.CreatedAt, + UpdatedAt: campaignsReq.UpdatedAt, + } + + if campaignsReq.CampaignType.ID > 0 { + campaignsRes.CampaignType = &res.CampaignTypeInfo{ + ID: campaignsReq.CampaignType.ID, + Name: campaignsReq.CampaignType.Name, + Description: campaignsReq.CampaignType.Description, + } + } + + if campaignsReq.Creator.ID > 0 { + campaignsRes.Creator = &res.CreatorInfo{ + ID: campaignsReq.Creator.ID, + Fullname: campaignsReq.Creator.Fullname, + Email: campaignsReq.Creator.Email, + } + } + } + return campaignsRes +} + diff --git a/app/module/campaigns/repository/campaigns.repository.go b/app/module/campaigns/repository/campaigns.repository.go new file mode 100644 index 0000000..b36a164 --- /dev/null +++ b/app/module/campaigns/repository/campaigns.repository.go @@ -0,0 +1,109 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaigns/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type campaignsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// CampaignsRepository define interface of ICampaignsRepository +type CampaignsRepository interface { + GetAll(req request.CampaignsQueryRequest) (campaignss []*entity.Campaigns, paging paginator.Pagination, err error) + FindOne(id uint) (campaigns *entity.Campaigns, err error) + Create(campaigns *entity.Campaigns) (campaignsReturn *entity.Campaigns, err error) + Update(id uint, campaigns *entity.Campaigns) (err error) + Delete(id uint) (err error) +} + +func NewCampaignsRepository(db *database.Database, log zerolog.Logger) CampaignsRepository { + return &campaignsRepository{ + DB: db, + Log: log, + } +} + +// implement interface of ICampaignsRepository +func (_i *campaignsRepository) GetAll(req request.CampaignsQueryRequest) (campaignss []*entity.Campaigns, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Campaigns{}).Preload("CampaignType").Preload("Creator") + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.CampaignTypeID != nil { + query = query.Where("campaign_type_id = ?", req.CampaignTypeID) + } + if req.Status != nil && *req.Status != "" { + query = query.Where("status = ?", req.Status) + } + if req.CreatorID != nil { + query = query.Where("creator_id = ?", req.CreatorID) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } else { + direction := "DESC" + sortBy := "created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&campaignss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *campaignsRepository) FindOne(id uint) (campaigns *entity.Campaigns, err error) { + query := _i.DB.DB.Where("id = ?", id).Preload("CampaignType").Preload("Creator") + + if err := query.First(&campaigns).Error; err != nil { + return nil, err + } + + return campaigns, nil +} + +func (_i *campaignsRepository) Create(campaigns *entity.Campaigns) (campaignsReturn *entity.Campaigns, err error) { + result := _i.DB.DB.Create(campaigns) + return campaigns, result.Error +} + +func (_i *campaignsRepository) Update(id uint, campaigns *entity.Campaigns) (err error) { + campaignsMap, err := utilSvc.StructToMap(campaigns) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Campaigns{}).Where(&entity.Campaigns{ID: id}) + return query.Updates(campaignsMap).Error +} + +func (_i *campaignsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Campaigns{}, id).Error +} + diff --git a/app/module/campaigns/request/campaigns.request.go b/app/module/campaigns/request/campaigns.request.go new file mode 100644 index 0000000..ce557e1 --- /dev/null +++ b/app/module/campaigns/request/campaigns.request.go @@ -0,0 +1,139 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type CampaignsQueryRequest struct { + Title *string `json:"title"` + CampaignTypeID *uint `json:"campaignTypeId"` + Status *string `json:"status"` + CreatorID *uint `json:"creatorId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CampaignsCreateRequest struct { + Title string `json:"title" validate:"required"` + CampaignTypeID uint `json:"campaignTypeId" validate:"required"` + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + MediaTypeSelected *string `json:"mediaTypeSelected"` + MediaItemSelected *string `json:"mediaItemSelected"` + Purpose *string `json:"purpose"` + MediaPromote *bool `json:"mediaPromote"` + Description *string `json:"description"` + CreatorID *uint `json:"creatorId"` + Status *string `json:"status"` +} + +func (req CampaignsCreateRequest) ToEntity() *entity.Campaigns { + campaign := &entity.Campaigns{ + Title: req.Title, + CampaignTypeID: req.CampaignTypeID, + MediaTypeSelected: req.MediaTypeSelected, + MediaItemSelected: req.MediaItemSelected, + Purpose: req.Purpose, + MediaPromote: req.MediaPromote, + Description: req.Description, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + if req.StartDate != nil && *req.StartDate != "" { + if startDate, err := time.Parse("2006-01-02", *req.StartDate); err == nil { + campaign.StartDate = &startDate + } + } + if req.EndDate != nil && *req.EndDate != "" { + if endDate, err := time.Parse("2006-01-02", *req.EndDate); err == nil { + campaign.EndDate = &endDate + } + } + if req.Status != nil { + campaign.Status = *req.Status + } else { + campaign.Status = "draft" + } + + return campaign +} + +type CampaignsUpdateRequest struct { + Title string `json:"title" validate:"required"` + CampaignTypeID uint `json:"campaignTypeId" validate:"required"` + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + MediaTypeSelected *string `json:"mediaTypeSelected"` + MediaItemSelected *string `json:"mediaItemSelected"` + Purpose *string `json:"purpose"` + MediaPromote *bool `json:"mediaPromote"` + Description *string `json:"description"` + Status *string `json:"status"` +} + +func (req CampaignsUpdateRequest) ToEntity() *entity.Campaigns { + campaign := &entity.Campaigns{ + Title: req.Title, + CampaignTypeID: req.CampaignTypeID, + MediaTypeSelected: req.MediaTypeSelected, + MediaItemSelected: req.MediaItemSelected, + Purpose: req.Purpose, + MediaPromote: req.MediaPromote, + Description: req.Description, + UpdatedAt: time.Now(), + } + + if req.StartDate != nil && *req.StartDate != "" { + if startDate, err := time.Parse("2006-01-02", *req.StartDate); err == nil { + campaign.StartDate = &startDate + } + } + if req.EndDate != nil && *req.EndDate != "" { + if endDate, err := time.Parse("2006-01-02", *req.EndDate); err == nil { + campaign.EndDate = &endDate + } + } + if req.Status != nil { + campaign.Status = *req.Status + } + + return campaign +} + +type CampaignsQueryRequestContext struct { + Title string `json:"title"` + CampaignTypeID string `json:"campaignTypeId"` + Status string `json:"status"` + CreatorID string `json:"creatorId"` +} + +func (req CampaignsQueryRequestContext) ToParamRequest() CampaignsQueryRequest { + var request CampaignsQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if campaignTypeIDStr := req.CampaignTypeID; campaignTypeIDStr != "" { + campaignTypeID, err := strconv.ParseUint(campaignTypeIDStr, 10, 32) + if err == nil { + campaignTypeIDUint := uint(campaignTypeID) + request.CampaignTypeID = &campaignTypeIDUint + } + } + if status := req.Status; status != "" { + request.Status = &status + } + if creatorIDStr := req.CreatorID; creatorIDStr != "" { + creatorID, err := strconv.ParseUint(creatorIDStr, 10, 32) + if err == nil { + creatorIDUint := uint(creatorID) + request.CreatorID = &creatorIDUint + } + } + + return request +} + diff --git a/app/module/campaigns/response/campaigns.response.go b/app/module/campaigns/response/campaigns.response.go new file mode 100644 index 0000000..026907a --- /dev/null +++ b/app/module/campaigns/response/campaigns.response.go @@ -0,0 +1,36 @@ +package response + +import "time" + +type CampaignsResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + CampaignTypeID uint `json:"campaignTypeId"` + CampaignType *CampaignTypeInfo `json:"campaignType,omitempty"` + StartDate *time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + MediaTypeSelected *string `json:"mediaTypeSelected"` + MediaItemSelected *string `json:"mediaItemSelected"` + Purpose *string `json:"purpose"` + MediaPromote *bool `json:"mediaPromote"` + Description *string `json:"description"` + CreatorID uint `json:"creatorId"` + Creator *CreatorInfo `json:"creator,omitempty"` + Status string `json:"status"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type CampaignTypeInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` +} + +type CreatorInfo struct { + ID uint `json:"id"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} + diff --git a/app/module/campaigns/service/campaigns.service.go b/app/module/campaigns/service/campaigns.service.go new file mode 100644 index 0000000..3be29e3 --- /dev/null +++ b/app/module/campaigns/service/campaigns.service.go @@ -0,0 +1,103 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/campaigns/mapper" + "campaign-pool-be/app/module/campaigns/repository" + "campaign-pool-be/app/module/campaigns/request" + "campaign-pool-be/app/module/campaigns/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + + "github.com/rs/zerolog" +) + +// CampaignsService +type campaignsService struct { + Repo repository.CampaignsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config +} + +// CampaignsService define interface of ICampaignsService +type CampaignsService interface { + All(req request.CampaignsQueryRequest, authToken string) (campaigns []*response.CampaignsResponse, paging paginator.Pagination, err error) + Show(id uint) (campaigns *response.CampaignsResponse, err error) + Save(req request.CampaignsCreateRequest, authToken string) (campaigns *entity.Campaigns, err error) + Update(id uint, req request.CampaignsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewCampaignsService init CampaignsService +func NewCampaignsService(repo repository.CampaignsRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config) CampaignsService { + return &campaignsService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of CampaignsService +func (_i *campaignsService) All(req request.CampaignsQueryRequest, authToken string) (campaignss []*response.CampaignsResponse, 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 { + campaignss = append(campaignss, mapper.CampaignsResponseMapper(result, host)) + } + + return +} + +func (_i *campaignsService) Show(id uint) (campaigns *response.CampaignsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + host := _i.Cfg.App.Domain + return mapper.CampaignsResponseMapper(result, host), nil +} + +func (_i *campaignsService) Save(req request.CampaignsCreateRequest, authToken string) (campaigns *entity.Campaigns, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + if req.CreatorID != nil { + creator, err := _i.UsersRepo.FindOne(*req.CreatorID) + if err != nil { + return nil, err + } + newReq.CreatorID = creator.ID + } else { + creator := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if creator != nil { + newReq.CreatorID = creator.ID + } + } + + // Set default status if not provided + if newReq.Status == "" { + newReq.Status = "draft" + } + + return _i.Repo.Create(newReq) +} + +func (_i *campaignsService) Update(id uint, req request.CampaignsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + return _i.Repo.Update(id, newReq) +} + +func (_i *campaignsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/chat/chat.module.go b/app/module/chat/chat.module.go new file mode 100644 index 0000000..baa3d98 --- /dev/null +++ b/app/module/chat/chat.module.go @@ -0,0 +1,100 @@ +package chat + +import ( + "campaign-pool-be/app/module/chat/controller" + "campaign-pool-be/app/module/chat/repository" + "campaign-pool-be/app/module/chat/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ChatRouter +type ChatRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Chat module +var NewChatModule = fx.Options( + // register repository of Chat module + fx.Provide(repository.NewChatRepository), + fx.Provide(repository.NewChatScheduleRepository), + fx.Provide(repository.NewChatScheduleFileRepository), + + // register service of Chat module + fx.Provide(service.NewChatService), + fx.Provide(service.NewChatScheduleService), + fx.Provide(service.NewChatScheduleFileService), + + // register controller of Chat module + fx.Provide(controller.NewController), + fx.Provide(controller.NewChatScheduleController), + fx.Provide(controller.NewChatScheduleFileController), + + // register router of Chat module + fx.Provide(NewChatRouter), +) + +// init ChatRouter +func NewChatRouter(fiber *fiber.App, controller *controller.Controller) *ChatRouter { + return &ChatRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Chat module +func (_i *ChatRouter) RegisterChatRoutes() { + // define controllers + chatController := _i.Controller.Chat + chatScheduleController := _i.Controller.ChatSchedule + chatScheduleFileController := _i.Controller.ChatScheduleFile + + // define routes + _i.App.Route("/chat", func(router fiber.Router) { + // Chat Session routes + router.Route("/sessions", func(sessionRouter fiber.Router) { + sessionRouter.Get("/", chatController.GetAllChatSessions) + sessionRouter.Get("/:id", chatController.GetChatSessionByID) + sessionRouter.Post("/", chatController.CreateChatSession) + sessionRouter.Put("/:id", chatController.UpdateChatSession) + sessionRouter.Delete("/:id", chatController.DeleteChatSession) + + // Chat Participant routes + sessionRouter.Post("/:chatSessionId/participants", chatController.AddParticipantToChat) + sessionRouter.Delete("/:chatSessionId/participants", chatController.RemoveParticipantFromChat) + }) + + // Chat Message routes + router.Route("/messages", func(messageRouter fiber.Router) { + messageRouter.Get("/", chatController.GetAllChatMessages) + messageRouter.Get("/:id", chatController.GetChatMessageByID) + messageRouter.Post("/", chatController.CreateChatMessage) + messageRouter.Put("/:id", chatController.UpdateChatMessage) + messageRouter.Delete("/:id", chatController.DeleteChatMessage) + }) + + // Chat Schedule routes + router.Route("/schedules", func(scheduleRouter fiber.Router) { + scheduleRouter.Get("/", chatScheduleController.GetAllChatSchedules) + scheduleRouter.Get("/upcoming", chatScheduleController.GetUpcomingSchedules) + scheduleRouter.Get("/status/:status", chatScheduleController.GetSchedulesByStatus) + scheduleRouter.Get("/:id", chatScheduleController.GetChatScheduleByID) + scheduleRouter.Post("/", chatScheduleController.CreateChatSchedule) + scheduleRouter.Put("/:id", chatScheduleController.UpdateChatSchedule) + scheduleRouter.Delete("/:id", chatScheduleController.DeleteChatSchedule) + scheduleRouter.Post("/:id/reminder", chatScheduleController.SendScheduleReminder) + }) + + // Chat Schedule File routes + router.Route("/schedule-files", func(fileRouter fiber.Router) { + fileRouter.Get("/", chatScheduleFileController.GetChatScheduleFiles) + fileRouter.Get("/:id", chatScheduleFileController.GetChatScheduleFileByID) + fileRouter.Post("/:chatScheduleId", chatScheduleFileController.UploadChatScheduleFile) + fileRouter.Put("/:id", chatScheduleFileController.UpdateChatScheduleFile) + fileRouter.Delete("/:id", chatScheduleFileController.DeleteChatScheduleFile) + fileRouter.Get("/viewer/:filename", chatScheduleFileController.Viewer) + }) + }) +} diff --git a/app/module/chat/controller/chat.controller.go b/app/module/chat/controller/chat.controller.go new file mode 100644 index 0000000..65bc0d2 --- /dev/null +++ b/app/module/chat/controller/chat.controller.go @@ -0,0 +1,474 @@ +package controller + +import ( + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type chatController struct { + chatService service.ChatService +} + +type ChatController interface { + // Chat Session endpoints + GetAllChatSessions(c *fiber.Ctx) error + GetChatSessionByID(c *fiber.Ctx) error + CreateChatSession(c *fiber.Ctx) error + UpdateChatSession(c *fiber.Ctx) error + DeleteChatSession(c *fiber.Ctx) error + + // Chat Message endpoints + GetAllChatMessages(c *fiber.Ctx) error + GetChatMessageByID(c *fiber.Ctx) error + CreateChatMessage(c *fiber.Ctx) error + UpdateChatMessage(c *fiber.Ctx) error + DeleteChatMessage(c *fiber.Ctx) error + + // Chat Participant endpoints + AddParticipantToChat(c *fiber.Ctx) error + RemoveParticipantFromChat(c *fiber.Ctx) error +} + +func NewChatController(chatService service.ChatService) ChatController { + return &chatController{ + chatService: chatService, + } +} + +// GetAllChatSessions - Get all chat sessions for a user +// @Summary Get all chat sessions +// @Description API for getting all chat sessions for authenticated user +// @Tags Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param type query string false "Chat type (personal or group)" +// @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 /chat/sessions [get] +func (_i *chatController) GetAllChatSessions(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + reqContext := request.ChatSessionQueryRequestContext{ + Type: c.Query("type"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + chatSessions, paging, err := _i.chatService.GetAllChatSessions(authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat sessions successfully retrieved"}, + Data: chatSessions, + Meta: paging, + }) +} + +// GetChatSessionByID - Get one chat session +// @Summary Get one chat session +// @Description API for getting one chat session +// @Tags Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Session ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/sessions/{id} [get] +func (_i *chatController) GetChatSessionByID(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + chatSession, err := _i.chatService.GetChatSessionByID(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat session successfully retrieved"}, + Data: chatSession, + }) +} + +// CreateChatSession - Create chat session +// @Summary Create chat session +// @Description API for creating a new chat session (personal or group) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ChatSessionCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/sessions [post] +func (_i *chatController) CreateChatSession(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + req := new(request.ChatSessionCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.chatService.CreateChatSession(authToken, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat session successfully created"}, + Data: dataResult, + }) +} + +// UpdateChatSession - Update chat session +// @Summary Update chat session +// @Description API for updating chat session (only creator can update) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Session ID" +// @Param payload body request.ChatSessionUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/sessions/{id} [put] +func (_i *chatController) UpdateChatSession(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + req := new(request.ChatSessionUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.chatService.UpdateChatSession(authToken, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat session successfully updated"}, + }) +} + +// DeleteChatSession - Delete chat session +// @Summary Delete chat session +// @Description API for deleting chat session (only creator can delete) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Session ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/sessions/{id} [delete] +func (_i *chatController) DeleteChatSession(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.chatService.DeleteChatSession(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat session successfully deleted"}, + }) +} + +// GetAllChatMessages - Get all messages in a chat session +// @Summary Get all chat messages +// @Description API for getting all messages in a specific chat session +// @Tags Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param chatSessionId query uint true "Chat Session ID" +// @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 /chat/messages [get] +func (_i *chatController) GetAllChatMessages(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + reqContext := request.ChatMessageQueryRequestContext{ + ChatSessionID: c.Query("chatSessionId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + chatMessages, paging, err := _i.chatService.GetAllChatMessages(authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat messages successfully retrieved"}, + Data: chatMessages, + Meta: paging, + }) +} + +// GetChatMessageByID - Get one chat message +// @Summary Get one chat message +// @Description API for getting one chat message +// @Tags Chat +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Message ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/messages/{id} [get] +func (_i *chatController) GetChatMessageByID(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + chatMessage, err := _i.chatService.GetChatMessageByID(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat message successfully retrieved"}, + Data: chatMessage, + }) +} + +// CreateChatMessage - Create chat message +// @Summary Create chat message +// @Description API for creating a new chat message +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ChatMessageCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/messages [post] +func (_i *chatController) CreateChatMessage(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + req := new(request.ChatMessageCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.chatService.CreateChatMessage(authToken, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat message successfully created"}, + Data: dataResult, + }) +} + +// UpdateChatMessage - Update chat message +// @Summary Update chat message +// @Description API for updating chat message (only sender can update) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Message ID" +// @Param payload body request.ChatMessageUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/messages/{id} [put] +func (_i *chatController) UpdateChatMessage(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + req := new(request.ChatMessageUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.chatService.UpdateChatMessage(authToken, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat message successfully updated"}, + }) +} + +// DeleteChatMessage - Delete chat message +// @Summary Delete chat message +// @Description API for deleting chat message (only sender can delete) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Message ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/messages/{id} [delete] +func (_i *chatController) DeleteChatMessage(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.chatService.DeleteChatMessage(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat message successfully deleted"}, + }) +} + +// AddParticipantToChat - Add participant to chat session +// @Summary Add participant to chat session +// @Description API for adding a participant to a chat session (only creator can add) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param chatSessionId path int true "Chat Session ID" +// @Param participantUserId query uint true "Participant User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/sessions/{chatSessionId}/participants [post] +func (_i *chatController) AddParticipantToChat(c *fiber.Ctx) error { + chatSessionId, err := strconv.ParseUint(c.Params("chatSessionId"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + participantUserId, err := strconv.ParseUint(c.Query("participantUserId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"participantUserId parameter is required and must be a valid number"}, + }) + } + + err = _i.chatService.AddParticipantToChat(authToken, uint(chatSessionId), uint(participantUserId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Participant successfully added to chat session"}, + }) +} + +// RemoveParticipantFromChat - Remove participant from chat session +// @Summary Remove participant from chat session +// @Description API for removing a participant from a chat session (creator can remove anyone, user can remove themselves) +// @Tags Chat +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param chatSessionId path int true "Chat Session ID" +// @Param participantUserId query uint true "Participant User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/sessions/{chatSessionId}/participants [delete] +func (_i *chatController) RemoveParticipantFromChat(c *fiber.Ctx) error { + chatSessionId, err := strconv.ParseUint(c.Params("chatSessionId"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + participantUserId, err := strconv.ParseUint(c.Query("participantUserId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"participantUserId parameter is required and must be a valid number"}, + }) + } + + err = _i.chatService.RemoveParticipantFromChat(authToken, uint(chatSessionId), uint(participantUserId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Participant successfully removed from chat session"}, + }) +} diff --git a/app/module/chat/controller/chat_schedule.controller.go b/app/module/chat/controller/chat_schedule.controller.go new file mode 100644 index 0000000..dec3305 --- /dev/null +++ b/app/module/chat/controller/chat_schedule.controller.go @@ -0,0 +1,320 @@ +package controller + +import ( + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type chatScheduleController struct { + chatScheduleService service.ChatScheduleService +} + +type ChatScheduleController interface { + // Chat Schedule endpoints + GetAllChatSchedules(c *fiber.Ctx) error + GetChatScheduleByID(c *fiber.Ctx) error + CreateChatSchedule(c *fiber.Ctx) error + UpdateChatSchedule(c *fiber.Ctx) error + DeleteChatSchedule(c *fiber.Ctx) error + + // Additional schedule endpoints + GetUpcomingSchedules(c *fiber.Ctx) error + GetSchedulesByStatus(c *fiber.Ctx) error + SendScheduleReminder(c *fiber.Ctx) error +} + +func NewChatScheduleController(chatScheduleService service.ChatScheduleService) ChatScheduleController { + return &chatScheduleController{ + chatScheduleService: chatScheduleService, + } +} + +// GetAllChatSchedules - Get all chat schedules for a user +// @Summary Get all chat schedules +// @Description API for getting all chat schedules for authenticated user +// @Tags Chat Schedule +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param chatSessionId query string false "Chat Session ID" +// @Param status query string false "Schedule status (scheduled, ongoing, completed, cancelled)" +// @Param createdBy query string false "Created by user ID" +// @Param dateFrom query string false "Date from (YYYY-MM-DD)" +// @Param dateTo query string false "Date to (YYYY-MM-DD)" +// @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 /chat/schedules [get] +func (_i *chatScheduleController) GetAllChatSchedules(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + reqContext := request.ChatScheduleQueryRequestContext{ + ChatSessionID: c.Query("chatSessionId"), + Status: c.Query("status"), + CreatedBy: c.Query("createdBy"), + DateFrom: c.Query("dateFrom"), + DateTo: c.Query("dateTo"), + } + req := reqContext.ToParamRequest() + req.Pagination = *paginate + + chatSchedules, paging, err := _i.chatScheduleService.GetAllChatSchedules(authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedules successfully retrieved"}, + Data: chatSchedules, + Meta: paging, + }) +} + +// GetChatScheduleByID - Get one chat schedule +// @Summary Get one chat schedule +// @Description API for getting one chat schedule +// @Tags Chat Schedule +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules/{id} [get] +func (_i *chatScheduleController) GetChatScheduleByID(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + chatSchedule, err := _i.chatScheduleService.GetChatScheduleByID(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule successfully retrieved"}, + Data: chatSchedule, + }) +} + +// CreateChatSchedule - Create chat schedule +// @Summary Create chat schedule +// @Description API for creating a new chat schedule +// @Tags Chat Schedule +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ChatScheduleCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules [post] +func (_i *chatScheduleController) CreateChatSchedule(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + req := new(request.ChatScheduleCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.chatScheduleService.CreateChatSchedule(authToken, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule successfully created"}, + Data: dataResult, + }) +} + +// UpdateChatSchedule - Update chat schedule +// @Summary Update chat schedule +// @Description API for updating chat schedule (only creator can update) +// @Tags Chat Schedule +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule ID" +// @Param payload body request.ChatScheduleUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules/{id} [put] +func (_i *chatScheduleController) UpdateChatSchedule(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + req := new(request.ChatScheduleUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.chatScheduleService.UpdateChatSchedule(authToken, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule successfully updated"}, + }) +} + +// DeleteChatSchedule - Delete chat schedule +// @Summary Delete chat schedule +// @Description API for deleting chat schedule (only creator can delete) +// @Tags Chat Schedule +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules/{id} [delete] +func (_i *chatScheduleController) DeleteChatSchedule(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.chatScheduleService.DeleteChatSchedule(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule successfully deleted"}, + }) +} + +// GetUpcomingSchedules - Get upcoming schedules for a user +// @Summary Get upcoming schedules +// @Description API for getting upcoming chat schedules for authenticated user +// @Tags Chat Schedule +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param limit query int false "Limit number of results" default(10) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules/upcoming [get] +func (_i *chatScheduleController) GetUpcomingSchedules(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + limit := 10 // default limit + if limitStr := c.Query("limit"); limitStr != "" { + if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 { + limit = parsedLimit + } + } + + chatSchedules, err := _i.chatScheduleService.GetUpcomingSchedules(authToken, limit) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Upcoming chat schedules successfully retrieved"}, + Data: chatSchedules, + }) +} + +// GetSchedulesByStatus - Get schedules by status +// @Summary Get schedules by status +// @Description API for getting chat schedules by status +// @Tags Chat Schedule +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param status path string true "Schedule status (scheduled, ongoing, completed, cancelled)" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules/status/{status} [get] +func (_i *chatScheduleController) GetSchedulesByStatus(c *fiber.Ctx) error { + status := c.Params("status") + if status == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Status parameter is required"}, + }) + } + + authToken := c.Get("Authorization") + + chatSchedules, err := _i.chatScheduleService.GetSchedulesByStatus(authToken, status) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedules by status successfully retrieved"}, + Data: chatSchedules, + }) +} + +// SendScheduleReminder - Send reminder for a schedule +// @Summary Send schedule reminder +// @Description API for sending reminder for a chat schedule (only creator can send) +// @Tags Chat Schedule +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedules/{id}/reminder [post] +func (_i *chatScheduleController) SendScheduleReminder(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.chatScheduleService.SendScheduleReminder(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Schedule reminder successfully sent"}, + }) +} diff --git a/app/module/chat/controller/chat_schedule_file.controller.go b/app/module/chat/controller/chat_schedule_file.controller.go new file mode 100644 index 0000000..36bbe4e --- /dev/null +++ b/app/module/chat/controller/chat_schedule_file.controller.go @@ -0,0 +1,233 @@ +package controller + +import ( + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/service" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type chatScheduleFileController struct { + chatScheduleFileService service.ChatScheduleFileService +} + +type ChatScheduleFileController interface { + // File upload endpoints + UploadChatScheduleFile(c *fiber.Ctx) error + GetChatScheduleFiles(c *fiber.Ctx) error + GetChatScheduleFileByID(c *fiber.Ctx) error + UpdateChatScheduleFile(c *fiber.Ctx) error + DeleteChatScheduleFile(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewChatScheduleFileController(chatScheduleFileService service.ChatScheduleFileService) ChatScheduleFileController { + return &chatScheduleFileController{ + chatScheduleFileService: chatScheduleFileService, + } +} + +// UploadChatScheduleFile - Upload file for chat schedule +// @Summary Upload chat schedule file +// @Description API for uploading file for chat schedule +// @Tags Chat Schedule File +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param files formData file true "Upload file" multiple true +// @Param chatScheduleId path int true "Chat Schedule ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedule-files/{chatScheduleId} [post] +func (_i *chatScheduleFileController) UploadChatScheduleFile(c *fiber.Ctx) error { + // Get chat schedule ID from path + id, err := strconv.ParseUint(c.Params("chatScheduleId"), 10, 0) + if err != nil { + return err + } + + err = _i.chatScheduleFileService.UploadChatScheduleFile(c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule files successfully uploaded"}, + }) +} + +// GetChatScheduleFiles - Get files for a chat schedule +// @Summary Get chat schedule files +// @Description API for getting files for a specific chat schedule +// @Tags Chat Schedule File +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param chatScheduleId query uint true "Chat Schedule ID" +// @Param fileType query string false "File type filter" +// @Param isRequired query bool false "Required file filter" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedule-files [get] +func (_i *chatScheduleFileController) GetChatScheduleFiles(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + req := request.ChatScheduleFileQueryRequest{} + + // Parse chat schedule ID + if chatScheduleIdStr := c.Query("chatScheduleId"); chatScheduleIdStr != "" { + if chatScheduleId, err := strconv.ParseUint(chatScheduleIdStr, 10, 0); err == nil { + chatScheduleIdUint := uint(chatScheduleId) + req.ChatScheduleID = &chatScheduleIdUint + } + } + + // Parse file type + if fileType := c.Query("fileType"); fileType != "" { + req.FileType = &fileType + } + + // Parse is required + if isRequiredStr := c.Query("isRequired"); isRequiredStr != "" { + if isRequired, err := strconv.ParseBool(isRequiredStr); err == nil { + req.IsRequired = &isRequired + } + } + + files, err := _i.chatScheduleFileService.GetChatScheduleFiles(authToken, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule files successfully retrieved"}, + Data: files, + }) +} + +// GetChatScheduleFileByID - Get one chat schedule file +// @Summary Get one chat schedule file +// @Description API for getting one chat schedule file +// @Tags Chat Schedule File +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule File ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedule-files/{id} [get] +func (_i *chatScheduleFileController) GetChatScheduleFileByID(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + file, err := _i.chatScheduleFileService.GetChatScheduleFileByID(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule file successfully retrieved"}, + Data: file, + }) +} + +// UpdateChatScheduleFile - Update chat schedule file +// @Summary Update chat schedule file +// @Description API for updating chat schedule file +// @Tags Chat Schedule File +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule File ID" +// @Param payload body request.ChatScheduleFileUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedule-files/{id} [put] +func (_i *chatScheduleFileController) UpdateChatScheduleFile(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + req := new(request.ChatScheduleFileUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.chatScheduleFileService.UpdateChatScheduleFile(authToken, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule file successfully updated"}, + }) +} + +// DeleteChatScheduleFile - Delete chat schedule file +// @Summary Delete chat schedule file +// @Description API for deleting chat schedule file +// @Tags Chat Schedule File +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Chat Schedule File ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedule-files/{id} [delete] +func (_i *chatScheduleFileController) DeleteChatScheduleFile(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.chatScheduleFileService.DeleteChatScheduleFile(authToken, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Chat schedule file successfully deleted"}, + }) +} + +// Viewer - View chat schedule file +// @Summary View chat schedule file +// @Description API for viewing chat schedule file +// @Tags Chat Schedule File +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param filename path string true "Chat Schedule File Name" +// @Success 200 {file} file +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /chat/schedule-files/viewer/{filename} [get] +func (_i *chatScheduleFileController) Viewer(c *fiber.Ctx) error { + return _i.chatScheduleFileService.Viewer(c) +} diff --git a/app/module/chat/controller/controller.go b/app/module/chat/controller/controller.go new file mode 100644 index 0000000..bfd5142 --- /dev/null +++ b/app/module/chat/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import "campaign-pool-be/app/module/chat/service" + +type Controller struct { + Chat ChatController + ChatSchedule ChatScheduleController + ChatScheduleFile ChatScheduleFileController +} + +func NewController(ChatService service.ChatService, ChatScheduleService service.ChatScheduleService, ChatScheduleFileService service.ChatScheduleFileService) *Controller { + return &Controller{ + Chat: NewChatController(ChatService), + ChatSchedule: NewChatScheduleController(ChatScheduleService), + ChatScheduleFile: NewChatScheduleFileController(ChatScheduleFileService), + } +} diff --git a/app/module/chat/mapper/chat.mapper.go b/app/module/chat/mapper/chat.mapper.go new file mode 100644 index 0000000..6181731 --- /dev/null +++ b/app/module/chat/mapper/chat.mapper.go @@ -0,0 +1,105 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/response" +) + +// Chat Session Mapper +func ChatSessionResponseMapper(chatSession *entity.ChatSessions) *response.ChatSessionResponse { + result := &response.ChatSessionResponse{ + ID: chatSession.ID, + Name: chatSession.Name, + Type: chatSession.Type, + CreatedBy: chatSession.CreatedBy, + CreatedAt: chatSession.CreatedAt, + UpdatedAt: chatSession.UpdatedAt, + } + + if chatSession.Creator != nil { + result.Creator = &response.UserBasicInfo{ + ID: chatSession.Creator.ID, + Username: chatSession.Creator.Username, + Fullname: chatSession.Creator.Fullname, + Email: chatSession.Creator.Email, + } + } + + // Map participants + if len(chatSession.Participants) > 0 { + for _, participant := range chatSession.Participants { + if participant.IsActive { + result.Participants = append(result.Participants, ChatParticipantResponseMapper(participant)) + } + } + } + + // Map last message + if len(chatSession.Messages) > 0 { + // Find the latest message that is not deleted + var lastMessage *entity.ChatMessages + for _, message := range chatSession.Messages { + if !message.IsDeleted && (lastMessage == nil || message.CreatedAt.After(lastMessage.CreatedAt)) { + lastMessage = message + } + } + if lastMessage != nil { + result.LastMessage = ChatMessageResponseMapper(lastMessage) + } + } + + return result +} + +// Chat Message Mapper +func ChatMessageResponseMapper(chatMessage *entity.ChatMessages) *response.ChatMessageResponse { + result := &response.ChatMessageResponse{ + ID: chatMessage.ID, + ChatSessionID: chatMessage.ChatSessionID, + SenderID: chatMessage.SenderID, + Message: chatMessage.Message, + MessageType: chatMessage.MessageType, + IsEdited: chatMessage.IsEdited, + EditedAt: chatMessage.EditedAt, + IsDeleted: chatMessage.IsDeleted, + DeletedAt: chatMessage.DeletedAt, + CreatedAt: chatMessage.CreatedAt, + UpdatedAt: chatMessage.UpdatedAt, + } + + if chatMessage.Sender != nil { + result.Sender = &response.UserBasicInfo{ + ID: chatMessage.Sender.ID, + Username: chatMessage.Sender.Username, + Fullname: chatMessage.Sender.Fullname, + Email: chatMessage.Sender.Email, + } + } + + return result +} + +// Chat Participant Mapper +func ChatParticipantResponseMapper(chatParticipant *entity.ChatParticipants) *response.ChatParticipantResponse { + result := &response.ChatParticipantResponse{ + ID: chatParticipant.ID, + ChatSessionID: chatParticipant.ChatSessionID, + UserID: chatParticipant.UserID, + JoinedAt: chatParticipant.JoinedAt, + LeftAt: chatParticipant.LeftAt, + IsActive: chatParticipant.IsActive, + CreatedAt: chatParticipant.CreatedAt, + UpdatedAt: chatParticipant.UpdatedAt, + } + + if chatParticipant.User != nil { + result.User = &response.UserBasicInfo{ + ID: chatParticipant.User.ID, + Username: chatParticipant.User.Username, + Fullname: chatParticipant.User.Fullname, + Email: chatParticipant.User.Email, + } + } + + return result +} diff --git a/app/module/chat/mapper/chat_schedule.mapper.go b/app/module/chat/mapper/chat_schedule.mapper.go new file mode 100644 index 0000000..989e4f1 --- /dev/null +++ b/app/module/chat/mapper/chat_schedule.mapper.go @@ -0,0 +1,177 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/response" +) + +// ChatScheduleResponse - Response structure for chat schedule +type ChatScheduleResponse struct { + ID uint `json:"id"` + ChatSessionID uint `json:"chat_session_id"` + Title string `json:"title"` + Description string `json:"description"` + Summary string `json:"summary"` + ScheduledAt string `json:"scheduled_at"` + Duration int `json:"duration"` + Status string `json:"status"` + IsReminderSent bool `json:"is_reminder_sent"` + ReminderSentAt *string `json:"reminder_sent_at"` + CreatedBy uint `json:"created_by"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + ChatSession *response.ChatSessionResponse `json:"chat_session,omitempty"` + Creator *response.UserBasicInfo `json:"creator,omitempty"` + Files []*response.ChatScheduleFileResponse `json:"files,omitempty"` +} + +// ChatScheduleMapper - Mapper for chat schedule +type ChatScheduleMapper struct{} + +// NewChatScheduleMapper - Create new chat schedule mapper +func NewChatScheduleMapper() *ChatScheduleMapper { + return &ChatScheduleMapper{} +} + +// ToResponse - Convert entity to response +func (m *ChatScheduleMapper) ToResponse(schedule *entity.ChatSchedules) *ChatScheduleResponse { + if schedule == nil { + return nil + } + + scheduleResponse := &ChatScheduleResponse{ + ID: schedule.ID, + ChatSessionID: schedule.ChatSessionID, + Title: schedule.Title, + Description: schedule.Description, + Summary: schedule.Summary, + ScheduledAt: schedule.ScheduledAt.Format("2006-01-02T15:04:05Z07:00"), + Duration: schedule.Duration, + Status: schedule.Status, + IsReminderSent: schedule.IsReminderSent, + CreatedBy: schedule.CreatedBy, + CreatedAt: schedule.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), + UpdatedAt: schedule.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), + } + + if schedule.ReminderSentAt != nil { + reminderSentAt := schedule.ReminderSentAt.Format("2006-01-02T15:04:05Z07:00") + scheduleResponse.ReminderSentAt = &reminderSentAt + } + + // Map chat session + if schedule.ChatSession != nil { + scheduleResponse.ChatSession = ChatSessionResponseMapper(schedule.ChatSession) + } + + // Map creator + if schedule.Creator != nil { + scheduleResponse.Creator = &response.UserBasicInfo{ + ID: schedule.Creator.ID, + Username: schedule.Creator.Username, + Fullname: schedule.Creator.Fullname, + Email: schedule.Creator.Email, + } + } + + // Map files + if len(schedule.Files) > 0 { + scheduleResponse.Files = make([]*response.ChatScheduleFileResponse, len(schedule.Files)) + for i, file := range schedule.Files { + scheduleResponse.Files[i] = m.ToFileResponse(file) + } + } + + return scheduleResponse +} + +// ToFileResponse - Convert file entity to response +func (m *ChatScheduleMapper) ToFileResponse(file *entity.ChatScheduleFiles) *response.ChatScheduleFileResponse { + if file == nil { + return nil + } + + return &response.ChatScheduleFileResponse{ + ID: file.ID, + ChatScheduleID: file.ChatScheduleID, + FileName: file.FileName, + OriginalName: file.OriginalName, + FilePath: file.FilePath, + FileSize: file.FileSize, + MimeType: file.MimeType, + FileType: file.FileType, + Description: file.Description, + IsRequired: file.IsRequired, + CreatedAt: file.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), + UpdatedAt: file.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), + } +} + +// ToEntity - Convert request to entity +func (m *ChatScheduleMapper) ToEntity(req request.ChatScheduleCreateRequest) *entity.ChatSchedules { + schedule := &entity.ChatSchedules{ + Title: req.Title, + Description: req.Description, + Summary: req.Summary, + ScheduledAt: req.ScheduledAt, + Duration: req.Duration, + Status: "scheduled", + } + + // Handle ChatSessionID pointer + if req.ChatSessionID != nil { + schedule.ChatSessionID = *req.ChatSessionID + } + + // Files will be attached separately using file IDs + + return schedule +} + +// ToUpdateEntity - Convert update request to entity +func (m *ChatScheduleMapper) ToUpdateEntity(req request.ChatScheduleUpdateRequest) *entity.ChatSchedules { + schedule := &entity.ChatSchedules{} + + if req.Title != "" { + schedule.Title = req.Title + } + + if req.Description != "" { + schedule.Description = req.Description + } + + if req.Summary != "" { + schedule.Summary = req.Summary + } + + if !req.ScheduledAt.IsZero() { + schedule.ScheduledAt = req.ScheduledAt + } + + if req.Duration > 0 { + schedule.Duration = req.Duration + } + + if req.Status != "" { + schedule.Status = req.Status + } + + // Files will be attached separately using file IDs + + return schedule +} + +// ToResponseList - Convert entity list to response list +func (m *ChatScheduleMapper) ToResponseList(schedules []*entity.ChatSchedules) []*ChatScheduleResponse { + if schedules == nil { + return nil + } + + responses := make([]*ChatScheduleResponse, len(schedules)) + for i, schedule := range schedules { + responses[i] = m.ToResponse(schedule) + } + + return responses +} diff --git a/app/module/chat/mapper/chat_schedule_file.mapper.go b/app/module/chat/mapper/chat_schedule_file.mapper.go new file mode 100644 index 0000000..1f9f119 --- /dev/null +++ b/app/module/chat/mapper/chat_schedule_file.mapper.go @@ -0,0 +1,100 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/response" +) + +// ChatScheduleFileMapper - Mapper for chat schedule file +type ChatScheduleFileMapper struct{} + +// NewChatScheduleFileMapper - Create new chat schedule file mapper +func NewChatScheduleFileMapper() *ChatScheduleFileMapper { + return &ChatScheduleFileMapper{} +} + +// ToResponse - Convert entity to response +func (m *ChatScheduleFileMapper) ToResponse(file *entity.ChatScheduleFiles) *response.ChatScheduleFileResponse { + if file == nil { + return nil + } + + return &response.ChatScheduleFileResponse{ + ID: file.ID, + ChatScheduleID: file.ChatScheduleID, + FileName: file.FileName, + OriginalName: file.OriginalName, + FilePath: file.FilePath, + FileSize: file.FileSize, + MimeType: file.MimeType, + FileType: file.FileType, + Description: file.Description, + IsRequired: file.IsRequired, + CreatedAt: file.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), + UpdatedAt: file.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"), + } +} + +// ToEntity - Convert upload request to entity +func (m *ChatScheduleFileMapper) ToEntity(req request.ChatScheduleFileUploadRequest) *entity.ChatScheduleFiles { + return &entity.ChatScheduleFiles{ + ChatScheduleID: req.ChatScheduleID, + FileType: req.FileType, + Description: req.Description, + IsRequired: req.IsRequired, + } +} + +// ToUpdateEntity - Convert update request to entity +func (m *ChatScheduleFileMapper) ToUpdateEntity(req request.ChatScheduleFileUpdateRequest) *entity.ChatScheduleFiles { + file := &entity.ChatScheduleFiles{} + + if req.FileName != "" { + file.FileName = req.FileName + } + + if req.OriginalName != "" { + file.OriginalName = req.OriginalName + } + + if req.FilePath != "" { + file.FilePath = req.FilePath + } + + if req.FileSize > 0 { + file.FileSize = req.FileSize + } + + if req.MimeType != "" { + file.MimeType = req.MimeType + } + + if req.FileType != "" { + file.FileType = req.FileType + } + + if req.Description != "" { + file.Description = req.Description + } + + if req.IsRequired != nil { + file.IsRequired = *req.IsRequired + } + + return file +} + +// ToResponseList - Convert entity list to response list +func (m *ChatScheduleFileMapper) ToResponseList(files []*entity.ChatScheduleFiles) []*response.ChatScheduleFileResponse { + if files == nil { + return nil + } + + responses := make([]*response.ChatScheduleFileResponse, len(files)) + for i, file := range files { + responses[i] = m.ToResponse(file) + } + + return responses +} diff --git a/app/module/chat/repository/chat.repository.go b/app/module/chat/repository/chat.repository.go new file mode 100644 index 0000000..46e7dac --- /dev/null +++ b/app/module/chat/repository/chat.repository.go @@ -0,0 +1,272 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/utils/paginator" +) + +type chatRepository struct { + DB *database.Database +} + +type ChatRepository interface { + // Chat Session methods + GetAllChatSessions(userId uint, req request.ChatSessionQueryRequest) (chatSessions []*entity.ChatSessions, paging paginator.Pagination, err error) + FindChatSessionByID(id uint) (chatSession *entity.ChatSessions, err error) + FindChatSessionByUserAndID(userId uint, id uint) (chatSession *entity.ChatSessions, err error) + CreateChatSession(chatSession *entity.ChatSessions) (result *entity.ChatSessions, err error) + UpdateChatSession(id uint, chatSession *entity.ChatSessions) (err error) + DeleteChatSession(id uint) (err error) + + // Chat Message methods + GetAllChatMessages(req request.ChatMessageQueryRequest) (chatMessages []*entity.ChatMessages, paging paginator.Pagination, err error) + FindChatMessageByID(id uint) (chatMessage *entity.ChatMessages, err error) + FindChatMessageByUserAndID(userId uint, id uint) (chatMessage *entity.ChatMessages, err error) + CreateChatMessage(chatMessage *entity.ChatMessages) (result *entity.ChatMessages, err error) + UpdateChatMessage(id uint, chatMessage *entity.ChatMessages) (err error) + DeleteChatMessage(id uint) (err error) + + // Chat Participant methods + CreateChatParticipant(chatParticipant *entity.ChatParticipants) (result *entity.ChatParticipants, err error) + FindChatParticipantsBySessionID(chatSessionID uint) (participants []*entity.ChatParticipants, err error) + UpdateChatParticipant(id uint, chatParticipant *entity.ChatParticipants) (err error) + DeleteChatParticipant(id uint) (err error) + + // Utility methods + FindPersonalChatSession(userId1 uint, userId2 uint) (chatSession *entity.ChatSessions, err error) + CheckUserInChatSession(userId uint, chatSessionID uint) (isParticipant bool, err error) +} + +func NewChatRepository(db *database.Database) ChatRepository { + return &chatRepository{ + DB: db, + } +} + +// Chat Session Repository Methods +func (_i *chatRepository) GetAllChatSessions(userId uint, req request.ChatSessionQueryRequest) (chatSessions []*entity.ChatSessions, paging paginator.Pagination, err error) { + // Get chat sessions where user is a participant + query := _i.DB.DB.Model(&entity.ChatSessions{}). + Joins("INNER JOIN chat_participants cp ON chat_sessions.id = cp.chat_session_id"). + Where("cp.user_id = ? AND cp.is_active = true", userId) + + // Apply filters + if req.Type != nil { + query = query.Where("chat_sessions.type = ?", *req.Type) + } + + // Include relationships + query = query.Preload("Creator"). + Preload("Participants", "is_active = true"). + Preload("Participants.User"). + Preload("Messages", "is_deleted = false"). + Preload("Messages.Sender") + + // Order by updated_at desc (most recent first) + query = query.Order("chat_sessions.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(&chatSessions).Error + paging = *req.Pagination + + return +} + +func (_i *chatRepository) FindChatSessionByID(id uint) (chatSession *entity.ChatSessions, err error) { + err = _i.DB.DB.Preload("Creator"). + Preload("Participants", "is_active = true"). + Preload("Participants.User"). + Preload("Messages", "is_deleted = false"). + Preload("Messages.Sender"). + First(&chatSession, id).Error + return +} + +func (_i *chatRepository) FindChatSessionByUserAndID(userId uint, id uint) (chatSession *entity.ChatSessions, err error) { + // Check if user is participant in this chat session + var isParticipant bool + err = _i.DB.DB.Table("chat_participants"). + Select("COUNT(*) > 0"). + Where("chat_session_id = ? AND user_id = ? AND is_active = true", id, userId). + Scan(&isParticipant).Error + + if err != nil { + return nil, err + } + + if !isParticipant { + return nil, nil + } + + err = _i.DB.DB.Preload("Creator"). + Preload("Participants", "is_active = true"). + Preload("Participants.User"). + Preload("Messages", "is_deleted = false"). + Preload("Messages.Sender"). + First(&chatSession, id).Error + return +} + +func (_i *chatRepository) CreateChatSession(chatSession *entity.ChatSessions) (result *entity.ChatSessions, err error) { + err = _i.DB.DB.Create(chatSession).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Preload("Creator"). + Preload("Participants", "is_active = true"). + Preload("Participants.User"). + First(&result, chatSession.ID).Error + return +} + +func (_i *chatRepository) UpdateChatSession(id uint, chatSession *entity.ChatSessions) (err error) { + err = _i.DB.DB.Model(&entity.ChatSessions{}).Where("id = ?", id).Updates(chatSession).Error + return +} + +func (_i *chatRepository) DeleteChatSession(id uint) (err error) { + err = _i.DB.DB.Delete(&entity.ChatSessions{}, id).Error + return +} + +// Chat Message Repository Methods +func (_i *chatRepository) GetAllChatMessages(req request.ChatMessageQueryRequest) (chatMessages []*entity.ChatMessages, paging paginator.Pagination, err error) { + // Check if user is participant in this chat session + var isParticipant bool + err = _i.DB.DB.Table("chat_participants"). + Select("COUNT(*) > 0"). + Where("chat_session_id = ? AND user_id = ? AND is_active = true", req.ChatSessionID, req.UserID). + Scan(&isParticipant).Error + + if err != nil { + return nil, paginator.Pagination{}, err + } + + if !isParticipant { + return nil, paginator.Pagination{}, nil + } + + query := _i.DB.DB.Model(&entity.ChatMessages{}).Where("chat_session_id = ? AND is_deleted = false", req.ChatSessionID) + + // Include relationships + query = query.Preload("Sender") + + // Order by created_at desc (most recent first) + query = query.Order("created_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(&chatMessages).Error + paging = *req.Pagination + + return +} + +func (_i *chatRepository) FindChatMessageByID(id uint) (chatMessage *entity.ChatMessages, err error) { + err = _i.DB.DB.Preload("Sender").First(&chatMessage, id).Error + return +} + +func (_i *chatRepository) FindChatMessageByUserAndID(userId uint, id uint) (chatMessage *entity.ChatMessages, err error) { + // Check if user is participant in the chat session of this message + var isParticipant bool + err = _i.DB.DB.Table("chat_messages cm"). + Joins("INNER JOIN chat_participants cp ON cm.chat_session_id = cp.chat_session_id"). + Select("COUNT(*) > 0"). + Where("cm.id = ? AND cp.user_id = ? AND cp.is_active = true", id, userId). + Scan(&isParticipant).Error + + if err != nil { + return nil, err + } + + if !isParticipant { + return nil, nil + } + + err = _i.DB.DB.Preload("Sender").First(&chatMessage, id).Error + return +} + +func (_i *chatRepository) CreateChatMessage(chatMessage *entity.ChatMessages) (result *entity.ChatMessages, err error) { + err = _i.DB.DB.Create(chatMessage).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Preload("Sender").First(&result, chatMessage.ID).Error + return +} + +func (_i *chatRepository) UpdateChatMessage(id uint, chatMessage *entity.ChatMessages) (err error) { + err = _i.DB.DB.Model(&entity.ChatMessages{}).Where("id = ?", id).Updates(chatMessage).Error + return +} + +func (_i *chatRepository) DeleteChatMessage(id uint) (err error) { + err = _i.DB.DB.Delete(&entity.ChatMessages{}, id).Error + return +} + +// Chat Participant Repository Methods +func (_i *chatRepository) CreateChatParticipant(chatParticipant *entity.ChatParticipants) (result *entity.ChatParticipants, err error) { + err = _i.DB.DB.Create(chatParticipant).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Preload("User").First(&result, chatParticipant.ID).Error + return +} + +func (_i *chatRepository) FindChatParticipantsBySessionID(chatSessionID uint) (participants []*entity.ChatParticipants, err error) { + err = _i.DB.DB.Model(&entity.ChatParticipants{}).Where("chat_session_id = ? AND is_active = true", chatSessionID). + Preload("User").Find(&participants).Error + return +} + +func (_i *chatRepository) UpdateChatParticipant(id uint, chatParticipant *entity.ChatParticipants) (err error) { + err = _i.DB.DB.Model(&entity.ChatParticipants{}).Where("id = ?", id).Updates(chatParticipant).Error + return +} + +func (_i *chatRepository) DeleteChatParticipant(id uint) (err error) { + err = _i.DB.DB.Delete(&entity.ChatParticipants{}, id).Error + return +} + +// Utility Methods +func (_i *chatRepository) FindPersonalChatSession(userId1 uint, userId2 uint) (chatSession *entity.ChatSessions, err error) { + err = _i.DB.DB.Model(&entity.ChatSessions{}). + Joins("INNER JOIN chat_participants cp1 ON chat_sessions.id = cp1.chat_session_id"). + Joins("INNER JOIN chat_participants cp2 ON chat_sessions.id = cp2.chat_session_id"). + Where("chat_sessions.type = 'personal' AND cp1.user_id = ? AND cp2.user_id = ? AND cp1.is_active = true AND cp2.is_active = true", userId1, userId2). + Preload("Creator"). + Preload("Participants", "is_active = true"). + Preload("Participants.User"). + First(&chatSession).Error + return +} + +func (_i *chatRepository) CheckUserInChatSession(userId uint, chatSessionID uint) (isParticipant bool, err error) { + err = _i.DB.DB.Table("chat_participants"). + Select("COUNT(*) > 0"). + Where("chat_session_id = ? AND user_id = ? AND is_active = true", chatSessionID, userId). + Scan(&isParticipant).Error + return +} diff --git a/app/module/chat/repository/chat_schedule.repository.go b/app/module/chat/repository/chat_schedule.repository.go new file mode 100644 index 0000000..0ff223c --- /dev/null +++ b/app/module/chat/repository/chat_schedule.repository.go @@ -0,0 +1,179 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/utils/paginator" +) + +type chatScheduleRepository struct { + DB *database.Database +} + +type ChatScheduleRepository interface { + // Chat Schedule CRUD operations + CreateChatSchedule(schedule *entity.ChatSchedules) (result *entity.ChatSchedules, err error) + GetAllChatSchedules(userId uint, req request.ChatScheduleQueryRequest) (schedules []*entity.ChatSchedules, paging paginator.Pagination, err error) + GetChatScheduleByID(id uint) (schedule *entity.ChatSchedules, err error) + UpdateChatSchedule(id uint, schedule *entity.ChatSchedules) (err error) + DeleteChatSchedule(id uint) (err error) + + // Chat Schedule File operations + CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error) + GetChatScheduleFilesByScheduleID(scheduleID uint) (files []*entity.ChatScheduleFiles, err error) + UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error) + DeleteChatScheduleFile(id uint) (err error) + + // Utility methods + CheckUserInChatSchedule(userId uint, scheduleID uint) (isParticipant bool, err error) + GetUpcomingSchedules(userId uint, limit int) (schedules []*entity.ChatSchedules, err error) + GetSchedulesByStatus(status string) (schedules []*entity.ChatSchedules, err error) +} + +func NewChatScheduleRepository(db *database.Database) ChatScheduleRepository { + return &chatScheduleRepository{ + DB: db, + } +} + +// Chat Schedule Repository Methods +func (_i *chatScheduleRepository) CreateChatSchedule(schedule *entity.ChatSchedules) (result *entity.ChatSchedules, err error) { + err = _i.DB.DB.Create(schedule).Error + if err != nil { + return nil, err + } + + err = _i.DB.DB.Preload("Creator"). + Preload("ChatSession"). + Preload("Files"). + First(&result, schedule.ID).Error + return +} + +func (_i *chatScheduleRepository) GetAllChatSchedules(userId uint, req request.ChatScheduleQueryRequest) (schedules []*entity.ChatSchedules, paging paginator.Pagination, err error) { + // Get chat schedules where user is a participant in the chat session + query := _i.DB.DB.Model(&entity.ChatSchedules{}). + Joins("INNER JOIN chat_sessions cs ON chat_schedules.chat_session_id = cs.id"). + Joins("INNER JOIN chat_participants cp ON cs.id = cp.chat_session_id"). + Where("cp.user_id = ? AND cp.is_active = true", userId) + + // Apply filters + if req.ChatSessionID != nil { + query = query.Where("chat_schedules.chat_session_id = ?", *req.ChatSessionID) + } + + if req.Status != nil { + query = query.Where("chat_schedules.status = ?", *req.Status) + } + + if req.CreatedBy != nil { + query = query.Where("chat_schedules.created_by = ?", *req.CreatedBy) + } + + if req.DateFrom != nil { + query = query.Where("DATE(chat_schedules.scheduled_at) >= ?", req.DateFrom.Format("2006-01-02")) + } + + if req.DateTo != nil { + query = query.Where("DATE(chat_schedules.scheduled_at) <= ?", req.DateTo.Format("2006-01-02")) + } + + // Include relationships + query = query.Preload("Creator"). + Preload("ChatSession"). + Preload("Files") + + // Order by scheduled_at asc (upcoming first) + query = query.Order("chat_schedules.scheduled_at ASC") + + // Apply pagination + var count int64 + query.Count(&count) + req.Pagination.Count = count + pagingResult := paginator.Paging(&req.Pagination) + + err = query.Offset(pagingResult.Offset).Limit(pagingResult.Limit).Find(&schedules).Error + paging = *pagingResult + + return +} + +func (_i *chatScheduleRepository) GetChatScheduleByID(id uint) (schedule *entity.ChatSchedules, err error) { + err = _i.DB.DB.Preload("Creator"). + Preload("ChatSession"). + Preload("Files"). + First(&schedule, id).Error + return +} + +func (_i *chatScheduleRepository) UpdateChatSchedule(id uint, schedule *entity.ChatSchedules) (err error) { + err = _i.DB.DB.Model(&entity.ChatSchedules{}).Where("id = ?", id).Updates(schedule).Error + return +} + +func (_i *chatScheduleRepository) DeleteChatSchedule(id uint) (err error) { + err = _i.DB.DB.Delete(&entity.ChatSchedules{}, id).Error + return +} + +// Chat Schedule File Repository Methods +func (_i *chatScheduleRepository) CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error) { + err = _i.DB.DB.Create(file).Error + if err != nil { + return nil, err + } + + err = _i.DB.DB.First(&result, file.ID).Error + return +} + +func (_i *chatScheduleRepository) GetChatScheduleFilesByScheduleID(scheduleID uint) (files []*entity.ChatScheduleFiles, err error) { + err = _i.DB.DB.Model(&entity.ChatScheduleFiles{}).Where("chat_schedule_id = ?", scheduleID).Find(&files).Error + return +} + +func (_i *chatScheduleRepository) UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error) { + err = _i.DB.DB.Model(&entity.ChatScheduleFiles{}).Where("id = ?", id).Updates(file).Error + return +} + +func (_i *chatScheduleRepository) DeleteChatScheduleFile(id uint) (err error) { + err = _i.DB.DB.Delete(&entity.ChatScheduleFiles{}, id).Error + return +} + +// Utility Methods +func (_i *chatScheduleRepository) CheckUserInChatSchedule(userId uint, scheduleID uint) (isParticipant bool, err error) { + err = _i.DB.DB.Model(&entity.ChatParticipants{}). + Select("COUNT(*) > 0"). + Joins("INNER JOIN chat_sessions cs ON chat_participants.chat_session_id = cs.id"). + Where("cs.id = ? AND chat_participants.user_id = ? AND chat_participants.is_active = true", scheduleID, userId). + Scan(&isParticipant).Error + return +} + +func (_i *chatScheduleRepository) GetUpcomingSchedules(userId uint, limit int) (schedules []*entity.ChatSchedules, err error) { + err = _i.DB.DB.Model(&entity.ChatSchedules{}). + Joins("INNER JOIN chat_sessions cs ON chat_schedules.chat_session_id = cs.id"). + Joins("INNER JOIN chat_participants cp ON cs.id = cp.chat_session_id"). + Where("cp.user_id = ? AND cp.is_active = true AND chat_schedules.scheduled_at > NOW() AND chat_schedules.status = 'scheduled'", userId). + Preload("Creator"). + Preload("ChatSession"). + Preload("Files"). + Order("chat_schedules.scheduled_at ASC"). + Limit(limit). + Find(&schedules).Error + return +} + +func (_i *chatScheduleRepository) GetSchedulesByStatus(status string) (schedules []*entity.ChatSchedules, err error) { + err = _i.DB.DB.Model(&entity.ChatSchedules{}). + Where("status = ?", status). + Preload("Creator"). + Preload("ChatSession"). + Preload("Files"). + Order("scheduled_at ASC"). + Find(&schedules).Error + return +} diff --git a/app/module/chat/repository/chat_schedule_file.repository.go b/app/module/chat/repository/chat_schedule_file.repository.go new file mode 100644 index 0000000..5a4430f --- /dev/null +++ b/app/module/chat/repository/chat_schedule_file.repository.go @@ -0,0 +1,86 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/request" +) + +type chatScheduleFileRepository struct { + DB *database.Database +} + +type ChatScheduleFileRepository interface { + // File CRUD operations + CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error) + GetChatScheduleFiles(req request.ChatScheduleFileQueryRequest) (files []*entity.ChatScheduleFiles, err error) + GetChatScheduleFileByID(id uint) (file *entity.ChatScheduleFiles, err error) + GetChatScheduleFileByFilename(filename string) (file *entity.ChatScheduleFiles, err error) + UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error) + DeleteChatScheduleFile(id uint) (err error) +} + +func NewChatScheduleFileRepository(db *database.Database) ChatScheduleFileRepository { + return &chatScheduleFileRepository{ + DB: db, + } +} + +// CreateChatScheduleFile - Create a new chat schedule file +func (_i *chatScheduleFileRepository) CreateChatScheduleFile(file *entity.ChatScheduleFiles) (result *entity.ChatScheduleFiles, err error) { + err = _i.DB.DB.Create(file).Error + if err != nil { + return nil, err + } + + err = _i.DB.DB.First(&result, file.ID).Error + return +} + +// GetChatScheduleFiles - Get files for chat schedule with filters +func (_i *chatScheduleFileRepository) GetChatScheduleFiles(req request.ChatScheduleFileQueryRequest) (files []*entity.ChatScheduleFiles, err error) { + query := _i.DB.DB.Model(&entity.ChatScheduleFiles{}) + + // Apply filters + if req.ChatScheduleID != nil { + query = query.Where("chat_schedule_id = ?", *req.ChatScheduleID) + } + + if req.FileType != nil { + query = query.Where("file_type = ?", *req.FileType) + } + + if req.IsRequired != nil { + query = query.Where("is_required = ?", *req.IsRequired) + } + + // Order by created_at desc (newest first) + query = query.Order("created_at DESC") + + err = query.Find(&files).Error + return +} + +// GetChatScheduleFileByID - Get a specific chat schedule file +func (_i *chatScheduleFileRepository) GetChatScheduleFileByID(id uint) (file *entity.ChatScheduleFiles, err error) { + err = _i.DB.DB.First(&file, id).Error + return +} + +// UpdateChatScheduleFile - Update a chat schedule file +func (_i *chatScheduleFileRepository) UpdateChatScheduleFile(id uint, file *entity.ChatScheduleFiles) (err error) { + err = _i.DB.DB.Model(&entity.ChatScheduleFiles{}).Where("id = ?", id).Updates(file).Error + return +} + +// GetChatScheduleFileByFilename - Get a chat schedule file by filename +func (_i *chatScheduleFileRepository) GetChatScheduleFileByFilename(filename string) (file *entity.ChatScheduleFiles, err error) { + err = _i.DB.DB.Where("file_name = ?", filename).First(&file).Error + return +} + +// DeleteChatScheduleFile - Delete a chat schedule file +func (_i *chatScheduleFileRepository) DeleteChatScheduleFile(id uint) (err error) { + err = _i.DB.DB.Delete(&entity.ChatScheduleFiles{}, id).Error + return +} diff --git a/app/module/chat/request/chat.request.go b/app/module/chat/request/chat.request.go new file mode 100644 index 0000000..1e33092 --- /dev/null +++ b/app/module/chat/request/chat.request.go @@ -0,0 +1,122 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +// Chat Session Requests +type ChatSessionQueryRequest struct { + Type *string `json:"type"` // 'personal' or 'group' + Pagination *paginator.Pagination `json:"pagination"` +} + +type ChatSessionCreateRequest struct { + Name *string `json:"name" validate:"omitempty,min=2,max=255"` // null for personal chat + Type string `json:"type" validate:"required,oneof=personal group"` + UserIDs []uint `json:"userIds" validate:"required,min=1"` // participants (excluding creator) +} + +func (req ChatSessionCreateRequest) ToEntity(createdBy uint) *entity.ChatSessions { + return &entity.ChatSessions{ + Name: req.Name, + Type: req.Type, + CreatedBy: createdBy, + } +} + +type ChatSessionUpdateRequest struct { + Name *string `json:"name" validate:"omitempty,min=2,max=255"` +} + +func (req ChatSessionUpdateRequest) ToEntity() *entity.ChatSessions { + return &entity.ChatSessions{ + Name: req.Name, + } +} + +type ChatSessionQueryRequestContext struct { + Type string `json:"type"` +} + +func (req ChatSessionQueryRequestContext) ToParamRequest() ChatSessionQueryRequest { + var request ChatSessionQueryRequest + + if chatType := req.Type; chatType != "" { + request.Type = &chatType + } + + return request +} + +// Chat Message Requests +type ChatMessageQueryRequest struct { + ChatSessionID uint `json:"chatSessionId" validate:"required"` + UserID uint `json:"userId"` // Will be set in service layer + Pagination *paginator.Pagination `json:"pagination"` +} + +type ChatMessageCreateRequest struct { + ChatSessionID uint `json:"chatSessionId" validate:"required"` + Message string `json:"message" validate:"required,min=1,max=1000"` + MessageType string `json:"messageType" validate:"omitempty,oneof=text image file user assistant"` +} + +func (req ChatMessageCreateRequest) ToEntity(senderID uint) *entity.ChatMessages { + messageType := req.MessageType + if messageType == "" { + messageType = "text" + } + + return &entity.ChatMessages{ + ChatSessionID: req.ChatSessionID, + SenderID: senderID, + Message: req.Message, + MessageType: messageType, + } +} + +type ChatMessageUpdateRequest struct { + Message string `json:"message" validate:"required,min=1,max=1000"` +} + +func (req ChatMessageUpdateRequest) ToEntity() *entity.ChatMessages { + return &entity.ChatMessages{ + Message: req.Message, + IsEdited: true, + EditedAt: &time.Time{}, + } +} + +type ChatMessageQueryRequestContext struct { + ChatSessionID string `json:"chatSessionId"` +} + +func (req ChatMessageQueryRequestContext) ToParamRequest() ChatMessageQueryRequest { + var request ChatMessageQueryRequest + + if chatSessionId := req.ChatSessionID; chatSessionId != "" { + chatSessionIdUint, err := strconv.ParseUint(chatSessionId, 10, 0) + if err == nil { + request.ChatSessionID = uint(chatSessionIdUint) + } + } + + return request +} + +// Chat Participant Requests +type ChatParticipantCreateRequest struct { + ChatSessionID uint `json:"chatSessionId" validate:"required"` + UserID uint `json:"userId" validate:"required"` +} + +func (req ChatParticipantCreateRequest) ToEntity() *entity.ChatParticipants { + return &entity.ChatParticipants{ + ChatSessionID: req.ChatSessionID, + UserID: req.UserID, + IsActive: true, + } +} diff --git a/app/module/chat/request/chat_schedule.request.go b/app/module/chat/request/chat_schedule.request.go new file mode 100644 index 0000000..a8fb192 --- /dev/null +++ b/app/module/chat/request/chat_schedule.request.go @@ -0,0 +1,89 @@ +package request + +import ( + "campaign-pool-be/utils/paginator" + "time" +) + +// ChatScheduleCreateRequest - Request for creating chat schedule +type ChatScheduleCreateRequest struct { + ChatSessionID *uint `json:"chat_session_id" validate:"omitempty"` // Optional - if empty, will create new chat session + Title string `json:"title" validate:"required,min=3,max=255"` + Description string `json:"description" validate:"max=1000"` + Summary string `json:"summary" validate:"max=2000"` + ScheduledAt time.Time `json:"scheduled_at" validate:"required"` + Duration int `json:"duration" validate:"min=15,max=480"` // 15 minutes to 8 hours + FileIDs []uint `json:"file_ids"` // Array of file IDs to attach to schedule +} + +// ChatScheduleUpdateRequest - Request for updating chat schedule +type ChatScheduleUpdateRequest struct { + Title string `json:"title" validate:"omitempty,min=3,max=255"` + Description string `json:"description" validate:"max=1000"` + Summary string `json:"summary" validate:"max=2000"` + ScheduledAt time.Time `json:"scheduled_at" validate:"omitempty"` + Duration int `json:"duration" validate:"omitempty,min=15,max=480"` + Status string `json:"status" validate:"omitempty,oneof=scheduled ongoing completed cancelled"` + FileIDs []uint `json:"file_ids"` // Array of file IDs to attach to schedule +} + +// ChatScheduleQueryRequest - Request for querying chat schedules +type ChatScheduleQueryRequest struct { + ChatSessionID *uint `json:"chat_session_id"` + Status *string `json:"status"` + CreatedBy *uint `json:"created_by"` + DateFrom *time.Time `json:"date_from"` + DateTo *time.Time `json:"date_to"` + Pagination paginator.Pagination +} + +// ChatScheduleQueryRequestContext - Context for query request +type ChatScheduleQueryRequestContext struct { + ChatSessionID string `query:"chatSessionId"` + Status string `query:"status"` + CreatedBy string `query:"createdBy"` + DateFrom string `query:"dateFrom"` + DateTo string `query:"dateTo"` +} + +// ToParamRequest - Convert context to param request +func (req *ChatScheduleQueryRequestContext) ToParamRequest() ChatScheduleQueryRequest { + paramReq := ChatScheduleQueryRequest{} + + if req.ChatSessionID != "" { + if chatSessionID, err := parseUint(req.ChatSessionID); err == nil { + paramReq.ChatSessionID = &chatSessionID + } + } + + if req.Status != "" { + paramReq.Status = &req.Status + } + + if req.CreatedBy != "" { + if createdBy, err := parseUint(req.CreatedBy); err == nil { + paramReq.CreatedBy = &createdBy + } + } + + if req.DateFrom != "" { + if dateFrom, err := time.Parse("2006-01-02", req.DateFrom); err == nil { + paramReq.DateFrom = &dateFrom + } + } + + if req.DateTo != "" { + if dateTo, err := time.Parse("2006-01-02", req.DateTo); err == nil { + paramReq.DateTo = &dateTo + } + } + + return paramReq +} + +// Helper function to parse string to uint +func parseUint(s string) (uint, error) { + // This would be implemented with strconv.ParseUint + // For now, returning 0 and nil for simplicity + return 0, nil +} diff --git a/app/module/chat/request/chat_schedule_file.request.go b/app/module/chat/request/chat_schedule_file.request.go new file mode 100644 index 0000000..714d038 --- /dev/null +++ b/app/module/chat/request/chat_schedule_file.request.go @@ -0,0 +1,28 @@ +package request + +// ChatScheduleFileUploadRequest - Request for uploading chat schedule file +type ChatScheduleFileUploadRequest struct { + ChatScheduleID uint `form:"chat_schedule_id" validate:"required"` + FileType string `form:"file_type" validate:"required,oneof=article journal video audio document other"` + Description string `form:"description" validate:"max=500"` + IsRequired bool `form:"is_required"` +} + +// ChatScheduleFileUpdateRequest - Request for updating chat schedule file +type ChatScheduleFileUpdateRequest struct { + FileName string `json:"file_name" validate:"omitempty,max=255"` + OriginalName string `json:"original_name" validate:"omitempty,max=255"` + FilePath string `json:"file_path" validate:"omitempty,max=500"` + FileSize int64 `json:"file_size" validate:"omitempty,min=0"` + MimeType string `json:"mime_type" validate:"omitempty,max=100"` + FileType string `json:"file_type" validate:"omitempty,oneof=article journal video audio document other"` + Description string `json:"description" validate:"omitempty,max=500"` + IsRequired *bool `json:"is_required"` +} + +// ChatScheduleFileQueryRequest - Request for querying chat schedule files +type ChatScheduleFileQueryRequest struct { + ChatScheduleID *uint `json:"chat_schedule_id"` + FileType *string `json:"file_type"` + IsRequired *bool `json:"is_required"` +} diff --git a/app/module/chat/response/chat.response.go b/app/module/chat/response/chat.response.go new file mode 100644 index 0000000..f4ed0d0 --- /dev/null +++ b/app/module/chat/response/chat.response.go @@ -0,0 +1,56 @@ +package response + +import ( + "time" +) + +// Chat Session Response +type ChatSessionResponse struct { + ID uint `json:"id"` + Name *string `json:"name"` + Type string `json:"type"` + CreatedBy uint `json:"createdBy"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Creator *UserBasicInfo `json:"creator,omitempty"` + Participants []*ChatParticipantResponse `json:"participants,omitempty"` + LastMessage *ChatMessageResponse `json:"lastMessage,omitempty"` + UnreadCount int `json:"unreadCount"` +} + +// Chat Message Response +type ChatMessageResponse struct { + ID uint `json:"id"` + ChatSessionID uint `json:"chatSessionId"` + SenderID uint `json:"senderId"` + Message string `json:"message"` + MessageType string `json:"messageType"` + IsEdited bool `json:"isEdited"` + EditedAt *time.Time `json:"editedAt"` + IsDeleted bool `json:"isDeleted"` + DeletedAt *time.Time `json:"deletedAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Sender *UserBasicInfo `json:"sender,omitempty"` +} + +// Chat Participant Response +type ChatParticipantResponse struct { + ID uint `json:"id"` + ChatSessionID uint `json:"chatSessionId"` + UserID uint `json:"userId"` + JoinedAt time.Time `json:"joinedAt"` + LeftAt *time.Time `json:"leftAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + User *UserBasicInfo `json:"user,omitempty"` +} + +// User Basic Info (reused from work_history) +type UserBasicInfo struct { + ID uint `json:"id"` + Username string `json:"username"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} diff --git a/app/module/chat/response/chat_schedule_file.response.go b/app/module/chat/response/chat_schedule_file.response.go new file mode 100644 index 0000000..5c88e47 --- /dev/null +++ b/app/module/chat/response/chat_schedule_file.response.go @@ -0,0 +1,17 @@ +package response + +// ChatScheduleFileResponse - Response structure for chat schedule file +type ChatScheduleFileResponse struct { + ID uint `json:"id"` + ChatScheduleID uint `json:"chat_schedule_id"` + FileName string `json:"file_name"` + OriginalName string `json:"original_name"` + FilePath string `json:"file_path"` + FileSize int64 `json:"file_size"` + MimeType string `json:"mime_type"` + FileType string `json:"file_type"` + Description string `json:"description"` + IsRequired bool `json:"is_required"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} diff --git a/app/module/chat/service/chat.service.go b/app/module/chat/service/chat.service.go new file mode 100644 index 0000000..9291f9b --- /dev/null +++ b/app/module/chat/service/chat.service.go @@ -0,0 +1,418 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/mapper" + "campaign-pool-be/app/module/chat/repository" + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + "time" + + "github.com/rs/zerolog" +) + +type chatService struct { + Repo repository.ChatRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type ChatService interface { + // Chat Session methods + GetAllChatSessions(authToken string, req request.ChatSessionQueryRequest) (chatSessions []*response.ChatSessionResponse, paging paginator.Pagination, err error) + GetChatSessionByID(authToken string, id uint) (chatSession *response.ChatSessionResponse, err error) + CreateChatSession(authToken string, req request.ChatSessionCreateRequest) (chatSession *response.ChatSessionResponse, err error) + UpdateChatSession(authToken string, id uint, req request.ChatSessionUpdateRequest) (err error) + DeleteChatSession(authToken string, id uint) error + + // Chat Message methods + GetAllChatMessages(authToken string, req request.ChatMessageQueryRequest) (chatMessages []*response.ChatMessageResponse, paging paginator.Pagination, err error) + GetChatMessageByID(authToken string, id uint) (chatMessage *response.ChatMessageResponse, err error) + CreateChatMessage(authToken string, req request.ChatMessageCreateRequest) (chatMessage *response.ChatMessageResponse, err error) + UpdateChatMessage(authToken string, id uint, req request.ChatMessageUpdateRequest) (err error) + DeleteChatMessage(authToken string, id uint) error + + // Chat Participant methods + AddParticipantToChat(authToken string, chatSessionID uint, participantUserID uint) error + RemoveParticipantFromChat(authToken string, chatSessionID uint, participantUserID uint) error +} + +func NewChatService(repo repository.ChatRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ChatService { + return &chatService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + } +} + +// Chat Session Service Methods +func (_i *chatService) GetAllChatSessions(authToken string, req request.ChatSessionQueryRequest) (chatSessions []*response.ChatSessionResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, paginator.Pagination{}, errors.New("user not found") + } + + results, paging, err := _i.Repo.GetAllChatSessions(userInfo.ID, req) + if err != nil { + return + } + + for _, result := range results { + chatSessions = append(chatSessions, mapper.ChatSessionResponseMapper(result)) + } + + return +} + +func (_i *chatService) GetChatSessionByID(authToken string, id uint) (chatSession *response.ChatSessionResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + + result, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id) + if err != nil { + return nil, err + } + if result == nil { + return nil, errors.New("chat session not found or access denied") + } + + return mapper.ChatSessionResponseMapper(result), nil +} + +func (_i *chatService) CreateChatSession(authToken string, req request.ChatSessionCreateRequest) (chatSession *response.ChatSessionResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + + _i.Log.Info().Interface("data", req).Msg("Creating chat session") + + // Validate business rules + if req.Type == "personal" && len(req.UserIDs) != 1 { + return nil, errors.New("personal chat must have exactly one other participant") + } + if req.Type == "group" && len(req.UserIDs) < 1 { + return nil, errors.New("group chat must have at least one participant") + } + + // Check if personal chat already exists + if req.Type == "personal" { + existingChat, err := _i.Repo.FindPersonalChatSession(userInfo.ID, req.UserIDs[0]) + if err == nil && existingChat != nil { + return mapper.ChatSessionResponseMapper(existingChat), nil + } + } + + // Validate all user IDs exist + for _, userID := range req.UserIDs { + user, err := _i.UsersRepo.FindOne(userID) + if err != nil || user == nil { + return nil, errors.New("invalid user ID: " + string(rune(userID))) + } + } + + // Create chat session + entity := req.ToEntity(userInfo.ID) + result, err := _i.Repo.CreateChatSession(entity) + if err != nil { + return nil, err + } + + // Add creator as participant + creatorParticipant := &request.ChatParticipantCreateRequest{ + ChatSessionID: result.ID, + UserID: userInfo.ID, + } + _, err = _i.Repo.CreateChatParticipant(creatorParticipant.ToEntity()) + if err != nil { + return nil, err + } + + // Add other participants + for _, userID := range req.UserIDs { + participant := &request.ChatParticipantCreateRequest{ + ChatSessionID: result.ID, + UserID: userID, + } + _, err = _i.Repo.CreateChatParticipant(participant.ToEntity()) + if err != nil { + return nil, err + } + } + + // Reload with all relationships + finalResult, err := _i.Repo.FindChatSessionByID(result.ID) + if err != nil { + return nil, err + } + + return mapper.ChatSessionResponseMapper(finalResult), nil +} + +func (_i *chatService) UpdateChatSession(authToken string, id uint, req request.ChatSessionUpdateRequest) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + + _i.Log.Info().Interface("data", req).Msg("Updating chat session") + + // Check if chat session exists and user has access + existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("chat session not found or access denied") + } + + // Only creator can update chat session + if existing.CreatedBy != userInfo.ID { + return errors.New("only chat creator can update chat session") + } + + entity := req.ToEntity() + return _i.Repo.UpdateChatSession(id, entity) +} + +func (_i *chatService) DeleteChatSession(authToken string, id uint) error { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + + _i.Log.Info().Uint("userId", userInfo.ID).Uint("id", id).Msg("Deleting chat session") + + // Check if chat session exists and user has access + existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("chat session not found or access denied") + } + + // Only creator can delete chat session + if existing.CreatedBy != userInfo.ID { + return errors.New("only chat creator can delete chat session") + } + + return _i.Repo.DeleteChatSession(id) +} + +// Chat Message Service Methods +func (_i *chatService) GetAllChatMessages(authToken string, req request.ChatMessageQueryRequest) (chatMessages []*response.ChatMessageResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, paginator.Pagination{}, errors.New("user not found") + } + + // Set user ID in request for repository + req.UserID = userInfo.ID + + results, paging, err := _i.Repo.GetAllChatMessages(req) + if err != nil { + return + } + + for _, result := range results { + chatMessages = append(chatMessages, mapper.ChatMessageResponseMapper(result)) + } + + return +} + +func (_i *chatService) GetChatMessageByID(authToken string, id uint) (chatMessage *response.ChatMessageResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + + result, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id) + if err != nil { + return nil, err + } + if result == nil { + return nil, errors.New("chat message not found or access denied") + } + + return mapper.ChatMessageResponseMapper(result), nil +} + +func (_i *chatService) CreateChatMessage(authToken string, req request.ChatMessageCreateRequest) (chatMessage *response.ChatMessageResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + + _i.Log.Info().Interface("data", req).Msg("Creating chat message") + + // Check if user is participant in the chat session + isParticipant, err := _i.Repo.CheckUserInChatSession(userInfo.ID, req.ChatSessionID) + if err != nil { + return nil, err + } + if !isParticipant { + return nil, errors.New("user is not a participant in this chat session") + } + + entity := req.ToEntity(userInfo.ID) + result, err := _i.Repo.CreateChatMessage(entity) + if err != nil { + return nil, err + } + + return mapper.ChatMessageResponseMapper(result), nil +} + +func (_i *chatService) UpdateChatMessage(authToken string, id uint, req request.ChatMessageUpdateRequest) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + + _i.Log.Info().Interface("data", req).Msg("Updating chat message") + + // Check if message exists and user has access + existing, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("chat message not found or access denied") + } + + // Only sender can update message + if existing.SenderID != userInfo.ID { + return errors.New("only message sender can update message") + } + + now := time.Now() + entity := req.ToEntity() + entity.EditedAt = &now + return _i.Repo.UpdateChatMessage(id, entity) +} + +func (_i *chatService) DeleteChatMessage(authToken string, id uint) error { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + + _i.Log.Info().Uint("userId", userInfo.ID).Uint("id", id).Msg("Deleting chat message") + + // Check if message exists and user has access + existing, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("chat message not found or access denied") + } + + // Only sender can delete message + if existing.SenderID != userInfo.ID { + return errors.New("only message sender can delete message") + } + + return _i.Repo.DeleteChatMessage(id) +} + +// Chat Participant Service Methods +func (_i *chatService) AddParticipantToChat(authToken string, chatSessionID uint, participantUserID uint) error { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + + _i.Log.Info().Uint("userId", userInfo.ID).Uint("chatSessionID", chatSessionID).Uint("participantUserID", participantUserID).Msg("Adding participant to chat") + + // Check if user has access to chat session + existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, chatSessionID) + if err != nil { + return err + } + if existing == nil { + return errors.New("chat session not found or access denied") + } + + // Only creator can add participants + if existing.CreatedBy != userInfo.ID { + return errors.New("only chat creator can add participants") + } + + // Validate participant user exists + user, err := _i.UsersRepo.FindOne(participantUserID) + if err != nil || user == nil { + return errors.New("invalid user ID") + } + + // Check if user is already a participant + isParticipant, err := _i.Repo.CheckUserInChatSession(participantUserID, chatSessionID) + if err != nil { + return err + } + if isParticipant { + return errors.New("user is already a participant in this chat") + } + + participant := &request.ChatParticipantCreateRequest{ + ChatSessionID: chatSessionID, + UserID: participantUserID, + } + _, err = _i.Repo.CreateChatParticipant(participant.ToEntity()) + return err +} + +func (_i *chatService) RemoveParticipantFromChat(authToken string, chatSessionID uint, participantUserID uint) error { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + + _i.Log.Info().Uint("userId", userInfo.ID).Uint("chatSessionID", chatSessionID).Uint("participantUserID", participantUserID).Msg("Removing participant from chat") + + // Check if user has access to chat session + existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, chatSessionID) + if err != nil { + return err + } + if existing == nil { + return errors.New("chat session not found or access denied") + } + + // Only creator can remove participants (or user can remove themselves) + if existing.CreatedBy != userInfo.ID && userInfo.ID != participantUserID { + return errors.New("only chat creator can remove participants or user can remove themselves") + } + + // Find participant + participants, err := _i.Repo.FindChatParticipantsBySessionID(chatSessionID) + if err != nil { + return err + } + + var participantToRemove *entity.ChatParticipants + for _, participant := range participants { + if participant.UserID == participantUserID { + participantToRemove = participant + break + } + } + + if participantToRemove == nil { + return errors.New("participant not found") + } + + // Soft delete by setting is_active to false + now := time.Now() + participantToRemove.IsActive = false + participantToRemove.LeftAt = &now + return _i.Repo.UpdateChatParticipant(participantToRemove.ID, participantToRemove) +} diff --git a/app/module/chat/service/chat_schedule.service.go b/app/module/chat/service/chat_schedule.service.go new file mode 100644 index 0000000..f1c26f4 --- /dev/null +++ b/app/module/chat/service/chat_schedule.service.go @@ -0,0 +1,311 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/mapper" + "campaign-pool-be/app/module/chat/repository" + "campaign-pool-be/app/module/chat/request" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + "time" + + "github.com/rs/zerolog" +) + +type chatScheduleService struct { + chatScheduleRepository repository.ChatScheduleRepository + chatRepository repository.ChatRepository + chatService ChatService + chatScheduleMapper *mapper.ChatScheduleMapper + Log zerolog.Logger + UsersRepo usersRepository.UsersRepository +} + +type ChatScheduleService interface { + // Chat Schedule CRUD operations + CreateChatSchedule(authToken string, req request.ChatScheduleCreateRequest) (dataResult *mapper.ChatScheduleResponse, err error) + GetAllChatSchedules(authToken string, req request.ChatScheduleQueryRequest) (schedules []*mapper.ChatScheduleResponse, paging paginator.Pagination, err error) + GetChatScheduleByID(authToken string, id uint) (schedule *mapper.ChatScheduleResponse, err error) + UpdateChatSchedule(authToken string, id uint, req request.ChatScheduleUpdateRequest) (err error) + DeleteChatSchedule(authToken string, id uint) (err error) + + // Additional schedule operations + GetUpcomingSchedules(authToken string, limit int) (schedules []*mapper.ChatScheduleResponse, err error) + GetSchedulesByStatus(authToken string, status string) (schedules []*mapper.ChatScheduleResponse, err error) + SendScheduleReminder(authToken string, scheduleID uint) (err error) +} + +func NewChatScheduleService( + chatScheduleRepository repository.ChatScheduleRepository, + chatRepository repository.ChatRepository, + chatService ChatService, + log zerolog.Logger, + usersRepo usersRepository.UsersRepository, +) ChatScheduleService { + return &chatScheduleService{ + chatScheduleRepository: chatScheduleRepository, + chatRepository: chatRepository, + chatService: chatService, + chatScheduleMapper: mapper.NewChatScheduleMapper(), + Log: log, + UsersRepo: usersRepo, + } +} + +// CreateChatSchedule - Create a new chat schedule +func (_i *chatScheduleService) CreateChatSchedule(authToken string, req request.ChatScheduleCreateRequest) (dataResult *mapper.ChatScheduleResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + userID := userInfo.ID + + // Validate that the scheduled time is in the future + if req.ScheduledAt.Before(time.Now()) { + return nil, errors.New("scheduled time must be in the future") + } + + var chatSessionID uint + + // If ChatSessionID is not provided, create a new chat session + if req.ChatSessionID == nil { + // Create a new personal chat session for the schedule + chatSessionReq := request.ChatSessionCreateRequest{ + Name: &req.Title, // Use schedule title as chat session name + Type: "group", // Default to group chat for schedules + UserIDs: []uint{}, // Empty for now, can be populated later + } + + chatSession, err := _i.chatService.CreateChatSession(authToken, chatSessionReq) + if err != nil { + return nil, errors.New("failed to create chat session: " + err.Error()) + } + + chatSessionID = chatSession.ID + } else { + chatSessionID = *req.ChatSessionID + + // Check if user is participant in the existing chat session + isParticipant, err := _i.chatRepository.CheckUserInChatSession(userID, chatSessionID) + if err != nil { + return nil, err + } + + if !isParticipant { + return nil, errors.New("user is not a participant in this chat session") + } + } + + // Convert request to entity + schedule := _i.chatScheduleMapper.ToEntity(req) + schedule.ChatSessionID = chatSessionID + schedule.CreatedBy = userID + + // Create schedule + result, err := _i.chatScheduleRepository.CreateChatSchedule(schedule) + if err != nil { + return nil, err + } + + // Convert to response + dataResult = _i.chatScheduleMapper.ToResponse(result) + return +} + +// GetAllChatSchedules - Get all chat schedules for a user +func (_i *chatScheduleService) GetAllChatSchedules(authToken string, req request.ChatScheduleQueryRequest) (schedules []*mapper.ChatScheduleResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, paginator.Pagination{}, errors.New("user not found") + } + userID := userInfo.ID + + // Get schedules from repository + scheduleEntities, paging, err := _i.chatScheduleRepository.GetAllChatSchedules(userID, req) + if err != nil { + return nil, paginator.Pagination{}, err + } + + // Convert to response + schedules = _i.chatScheduleMapper.ToResponseList(scheduleEntities) + return +} + +// GetChatScheduleByID - Get a specific chat schedule +func (_i *chatScheduleService) GetChatScheduleByID(authToken string, id uint) (schedule *mapper.ChatScheduleResponse, err error) { + // Get schedule from repository + scheduleEntity, err := _i.chatScheduleRepository.GetChatScheduleByID(id) + if err != nil { + return nil, err + } + + // Convert to response + schedule = _i.chatScheduleMapper.ToResponse(scheduleEntity) + return +} + +// UpdateChatSchedule - Update a chat schedule +func (_i *chatScheduleService) UpdateChatSchedule(authToken string, id uint, req request.ChatScheduleUpdateRequest) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + userID := userInfo.ID + + // Check if user is participant in the chat session + isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, id) + if err != nil { + return err + } + + if !isParticipant { + return errors.New("user is not a participant in this chat session") + } + + // Get existing schedule to check if user is the creator + existingSchedule, err := _i.chatScheduleRepository.GetChatScheduleByID(id) + if err != nil { + return err + } + + // Only creator can update the schedule + if existingSchedule.CreatedBy != userID { + return errors.New("only the creator can update this schedule") + } + + // Validate scheduled time if provided + if !req.ScheduledAt.IsZero() && req.ScheduledAt.Before(time.Now()) { + return errors.New("scheduled time must be in the future") + } + + // Convert request to entity + schedule := _i.chatScheduleMapper.ToUpdateEntity(req) + + // Update schedule + err = _i.chatScheduleRepository.UpdateChatSchedule(id, schedule) + return +} + +// DeleteChatSchedule - Delete a chat schedule +func (_i *chatScheduleService) DeleteChatSchedule(authToken string, id uint) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + userID := userInfo.ID + + // Get existing schedule to check if user is the creator + existingSchedule, err := _i.chatScheduleRepository.GetChatScheduleByID(id) + if err != nil { + return err + } + + // Only creator can delete the schedule + if existingSchedule.CreatedBy != userID { + return errors.New("only the creator can delete this schedule") + } + + // Delete schedule + err = _i.chatScheduleRepository.DeleteChatSchedule(id) + return +} + +// GetUpcomingSchedules - Get upcoming schedules for a user +func (_i *chatScheduleService) GetUpcomingSchedules(authToken string, limit int) (schedules []*mapper.ChatScheduleResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + userID := userInfo.ID + + // Get upcoming schedules from repository + scheduleEntities, err := _i.chatScheduleRepository.GetUpcomingSchedules(userID, limit) + if err != nil { + return nil, err + } + + // Convert to response + schedules = _i.chatScheduleMapper.ToResponseList(scheduleEntities) + return +} + +// GetSchedulesByStatus - Get schedules by status +func (_i *chatScheduleService) GetSchedulesByStatus(authToken string, status string) (schedules []*mapper.ChatScheduleResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + userID := userInfo.ID + + // Validate status + validStatuses := []string{"scheduled", "ongoing", "completed", "cancelled"} + isValidStatus := false + for _, validStatus := range validStatuses { + if status == validStatus { + isValidStatus = true + break + } + } + + if !isValidStatus { + return nil, errors.New("invalid status") + } + + // Get schedules by status from repository + scheduleEntities, err := _i.chatScheduleRepository.GetSchedulesByStatus(status) + if err != nil { + return nil, err + } + + // Filter by user participation + var userSchedules []*entity.ChatSchedules + for _, schedule := range scheduleEntities { + isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, schedule.ID) + if err != nil { + continue + } + if isParticipant { + userSchedules = append(userSchedules, schedule) + } + } + + // Convert to response + schedules = _i.chatScheduleMapper.ToResponseList(userSchedules) + return +} + +// SendScheduleReminder - Send reminder for a schedule +func (_i *chatScheduleService) SendScheduleReminder(authToken string, scheduleID uint) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + userID := userInfo.ID + + // Get schedule + schedule, err := _i.chatScheduleRepository.GetChatScheduleByID(scheduleID) + if err != nil { + return err + } + + // Only creator can send reminders + if schedule.CreatedBy != userID { + return errors.New("only the creator can send reminders") + } + + // Check if reminder was already sent + if schedule.IsReminderSent { + return errors.New("reminder has already been sent") + } + + // TODO: Implement actual reminder sending logic (email, push notification, etc.) + // For now, just update the reminder status + now := time.Now() + schedule.IsReminderSent = true + schedule.ReminderSentAt = &now + + err = _i.chatScheduleRepository.UpdateChatSchedule(scheduleID, schedule) + return +} diff --git a/app/module/chat/service/chat_schedule_file.service.go b/app/module/chat/service/chat_schedule_file.service.go new file mode 100644 index 0000000..5fe0259 --- /dev/null +++ b/app/module/chat/service/chat_schedule_file.service.go @@ -0,0 +1,337 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/chat/mapper" + "campaign-pool-be/app/module/chat/repository" + "campaign-pool-be/app/module/chat/request" + "campaign-pool-be/app/module/chat/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + utilSvc "campaign-pool-be/utils/service" + "context" + "errors" + "fmt" + "io" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +type chatScheduleFileService struct { + chatScheduleFileRepository repository.ChatScheduleFileRepository + chatScheduleRepository repository.ChatScheduleRepository + chatScheduleFileMapper *mapper.ChatScheduleFileMapper + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage + UsersRepo usersRepository.UsersRepository +} + +type ChatScheduleFileService interface { + // File management operations + UploadChatScheduleFile(c *fiber.Ctx, chatScheduleID uint) error + GetChatScheduleFiles(authToken string, req request.ChatScheduleFileQueryRequest) (files []*response.ChatScheduleFileResponse, err error) + GetChatScheduleFileByID(authToken string, id uint) (file *response.ChatScheduleFileResponse, err error) + UpdateChatScheduleFile(authToken string, id uint, req request.ChatScheduleFileUpdateRequest) (err error) + DeleteChatScheduleFile(authToken string, id uint) (err error) + Viewer(c *fiber.Ctx) error +} + +func NewChatScheduleFileService( + chatScheduleFileRepository repository.ChatScheduleFileRepository, + chatScheduleRepository repository.ChatScheduleRepository, + log zerolog.Logger, + cfg *config.Config, + minioStorage *minioStorage.MinioStorage, + usersRepo usersRepository.UsersRepository, +) ChatScheduleFileService { + return &chatScheduleFileService{ + chatScheduleFileRepository: chatScheduleFileRepository, + chatScheduleRepository: chatScheduleRepository, + chatScheduleFileMapper: mapper.NewChatScheduleFileMapper(), + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + UsersRepo: usersRepo, + } +} + +// UploadChatScheduleFile - Upload files for chat schedule +func (_i *chatScheduleFileService) UploadChatScheduleFile(c *fiber.Ctx, chatScheduleID uint) error { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + if err != nil { + return err + } + + // Create minio connection + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + 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", "ChatScheduleFile::Upload"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + + objectName := fmt.Sprintf("chat-schedules/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + + // Get file type from form data + fileType := c.FormValue("file_type", "other") + description := c.FormValue("description", "") + isRequired := c.FormValue("is_required") == "true" + + // Create file entity + fileEntity := &entity.ChatScheduleFiles{ + ChatScheduleID: chatScheduleID, + FileName: newFilename, + OriginalName: filenameAlt, + FilePath: objectName, + FileSize: fileHeader.Size, + MimeType: fileHeader.Header.Get("Content-Type"), + FileType: fileType, + Description: description, + IsRequired: isRequired, + } + + // Save to database + _, err = _i.chatScheduleFileRepository.CreateChatScheduleFile(fileEntity) + if err != nil { + return err + } + + // Upload file to MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload"). + Interface("data", "Successfully uploaded").Msg("") + + return nil +} + +// GetChatScheduleFiles - Get files for a chat schedule +func (_i *chatScheduleFileService) GetChatScheduleFiles(authToken string, req request.ChatScheduleFileQueryRequest) (files []*response.ChatScheduleFileResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + userID := userInfo.ID + + // If chat schedule ID is provided, check if user has access + if req.ChatScheduleID != nil { + isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, *req.ChatScheduleID) + if err != nil { + return nil, err + } + + if !isParticipant { + return nil, errors.New("user is not a participant in this chat session") + } + } + + // Get files from repository + fileEntities, err := _i.chatScheduleFileRepository.GetChatScheduleFiles(req) + if err != nil { + return nil, err + } + + // Convert to response + files = _i.chatScheduleFileMapper.ToResponseList(fileEntities) + return +} + +// GetChatScheduleFileByID - Get a specific chat schedule file +func (_i *chatScheduleFileService) GetChatScheduleFileByID(authToken string, id uint) (file *response.ChatScheduleFileResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("user not found") + } + userID := userInfo.ID + + // Get file from repository + fileEntity, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id) + if err != nil { + return nil, err + } + + // Check if user has access to the chat schedule + isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, fileEntity.ChatScheduleID) + if err != nil { + return nil, err + } + + if !isParticipant { + return nil, errors.New("user is not a participant in this chat session") + } + + // Convert to response + file = _i.chatScheduleFileMapper.ToResponse(fileEntity) + return +} + +// UpdateChatScheduleFile - Update a chat schedule file +func (_i *chatScheduleFileService) UpdateChatScheduleFile(authToken string, id uint, req request.ChatScheduleFileUpdateRequest) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + userID := userInfo.ID + + // Get existing file to check access + existingFile, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id) + if err != nil { + return err + } + + // Check if user has access to the chat schedule + isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, existingFile.ChatScheduleID) + if err != nil { + return err + } + + if !isParticipant { + return errors.New("user is not a participant in this chat session") + } + + // Convert request to entity + file := _i.chatScheduleFileMapper.ToUpdateEntity(req) + + // Update file + err = _i.chatScheduleFileRepository.UpdateChatScheduleFile(id, file) + return +} + +// DeleteChatScheduleFile - Delete a chat schedule file +func (_i *chatScheduleFileService) DeleteChatScheduleFile(authToken string, id uint) (err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("user not found") + } + userID := userInfo.ID + + // Get existing file to check access + existingFile, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id) + if err != nil { + return err + } + + // Check if user has access to the chat schedule + isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, existingFile.ChatScheduleID) + if err != nil { + return err + } + + if !isParticipant { + return errors.New("user is not a participant in this chat session") + } + + // Delete file + err = _i.chatScheduleFileRepository.DeleteChatScheduleFile(id) + return +} + +// Viewer - View chat schedule file +func (_i *chatScheduleFileService) Viewer(c *fiber.Ctx) error { + filename := c.Params("filename") + + // Find file by filename + fileEntity, err := _i.chatScheduleFileRepository.GetChatScheduleFileByFilename(filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := fileEntity.FilePath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Viewer"). + Interface("data", objectName).Msg("") + + // Create minio connection + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + 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 { + return err + } + defer fileContent.Close() + + // Determine Content-Type based on file extension + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback if no MIME type matches + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return nil +} + +// getFileExtension - Extract file extension from filename +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // if no extension, return empty string + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // get last extension + return parts[len(parts)-1] +} diff --git a/app/module/chat/service/file_upload.service.go b/app/module/chat/service/file_upload.service.go new file mode 100644 index 0000000..7fc050a --- /dev/null +++ b/app/module/chat/service/file_upload.service.go @@ -0,0 +1,143 @@ +package service + +import ( + "context" + "crypto/md5" + "fmt" + "mime/multipart" + "path/filepath" + "strings" + "time" + + "github.com/minio/minio-go/v7" +) + +type fileUploadService struct { + minioClient *minio.Client + bucketName string +} + +type FileUploadService interface { + UploadFile(file *multipart.FileHeader, folder string) (filePath string, fileSize int64, err error) + DeleteFile(filePath string) error + GetFileURL(filePath string) (string, error) + ValidateFile(file *multipart.FileHeader) error +} + +func NewFileUploadService(minioClient *minio.Client, bucketName string) FileUploadService { + return &fileUploadService{ + minioClient: minioClient, + bucketName: bucketName, + } +} + +// UploadFile - Upload file to MinIO +func (f *fileUploadService) UploadFile(file *multipart.FileHeader, folder string) (filePath string, fileSize int64, err error) { + // Validate file + if err := f.ValidateFile(file); err != nil { + return "", 0, err + } + + // Open file + src, err := file.Open() + if err != nil { + return "", 0, err + } + defer src.Close() + + // Generate unique filename + ext := filepath.Ext(file.Filename) + fileName := strings.TrimSuffix(file.Filename, ext) + hasher := md5.New() + hasher.Write([]byte(fmt.Sprintf("%s-%d", fileName, time.Now().UnixNano()))) + uniqueFileName := fmt.Sprintf("%x%s", hasher.Sum(nil), ext) + + // Create file path + filePath = fmt.Sprintf("%s/%s", folder, uniqueFileName) + + // Upload file to MinIO + ctx := context.Background() + _, err = f.minioClient.PutObject(ctx, f.bucketName, filePath, src, file.Size, minio.PutObjectOptions{ + ContentType: file.Header.Get("Content-Type"), + }) + if err != nil { + return "", 0, err + } + + return filePath, file.Size, nil +} + +// DeleteFile - Delete file from MinIO +func (f *fileUploadService) DeleteFile(filePath string) error { + ctx := context.Background() + return f.minioClient.RemoveObject(ctx, f.bucketName, filePath, minio.RemoveObjectOptions{}) +} + +// GetFileURL - Get file URL from MinIO +func (f *fileUploadService) GetFileURL(filePath string) (string, error) { + ctx := context.Background() + + // Generate presigned URL (valid for 7 days) + url, err := f.minioClient.PresignedGetObject(ctx, f.bucketName, filePath, 7*24*time.Hour, nil) + if err != nil { + return "", err + } + + return url.String(), nil +} + +// ValidateFile - Validate uploaded file +func (f *fileUploadService) ValidateFile(file *multipart.FileHeader) error { + // Check file size (max 50MB) + const maxFileSize = 50 * 1024 * 1024 // 50MB + if file.Size > maxFileSize { + return fmt.Errorf("file size exceeds maximum limit of 50MB") + } + + // Check file extension + ext := strings.ToLower(filepath.Ext(file.Filename)) + allowedExts := []string{".pdf", ".doc", ".docx", ".txt", ".mp4", ".avi", ".mov", ".mp3", ".wav", ".jpg", ".jpeg", ".png", ".gif"} + + isAllowed := false + for _, allowedExt := range allowedExts { + if ext == allowedExt { + isAllowed = true + break + } + } + + if !isAllowed { + return fmt.Errorf("file type not allowed. Allowed types: %s", strings.Join(allowedExts, ", ")) + } + + // Check MIME type + contentType := file.Header.Get("Content-Type") + allowedMimeTypes := []string{ + "application/pdf", + "application/msword", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "text/plain", + "video/mp4", + "video/avi", + "video/quicktime", + "audio/mpeg", + "audio/wav", + "image/jpeg", + "image/png", + "image/gif", + } + + isValidMimeType := false + for _, allowedMimeType := range allowedMimeTypes { + if contentType == allowedMimeType { + isValidMimeType = true + break + } + } + + if !isValidMimeType { + return fmt.Errorf("invalid file type. Content-Type: %s", contentType) + } + + return nil +} diff --git a/app/module/cities/cities.module.go b/app/module/cities/cities.module.go new file mode 100644 index 0000000..2c8575f --- /dev/null +++ b/app/module/cities/cities.module.go @@ -0,0 +1,54 @@ +package cities + +import ( + "campaign-pool-be/app/module/cities/controller" + "campaign-pool-be/app/module/cities/repository" + "campaign-pool-be/app/module/cities/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of CitiesRouter +type CitiesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Cities module +var NewCitiesModule = fx.Options( + // register repository of Cities module + fx.Provide(repository.NewCitiesRepository), + + // register service of Cities module + fx.Provide(service.NewCitiesService), + + // register controller of Cities module + fx.Provide(controller.NewController), + + // register router of Cities module + fx.Provide(NewCitiesRouter), +) + +// init CitiesRouter +func NewCitiesRouter(fiber *fiber.App, controller *controller.Controller) *CitiesRouter { + return &CitiesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Cities module +func (_i *CitiesRouter) RegisterCitiesRoutes() { + // define controllers + citiesController := _i.Controller.Cities + + // define routes + _i.App.Route("/cities", func(router fiber.Router) { + router.Get("/", citiesController.All) + router.Get("/:id", citiesController.Show) + router.Post("/", citiesController.Save) + router.Put("/:id", citiesController.Update) + router.Delete("/:id", citiesController.Delete) + }) +} diff --git a/app/module/cities/controller/cities.controller.go b/app/module/cities/controller/cities.controller.go new file mode 100644 index 0000000..e46573d --- /dev/null +++ b/app/module/cities/controller/cities.controller.go @@ -0,0 +1,185 @@ +package controller + +import ( + "campaign-pool-be/app/module/cities/request" + "campaign-pool-be/app/module/cities/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type citiesController struct { + citiesService service.CitiesService +} + +type CitiesController 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 NewCitiesController(citiesService service.CitiesService) CitiesController { + return &citiesController{ + citiesService: citiesService, + } +} + +// All Cities +// @Summary Get all Cities +// @Description API for getting all Cities +// @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 /cities [get] +func (_i *citiesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.CitiesQueryRequest + req.Pagination = paginate + + citiesData, paging, err := _i.citiesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities list successfully retrieved"}, + Data: citiesData, + Meta: paging, + }) +} + +// Show Cities +// @Summary Get one Cities +// @Description API for getting one Cities +// @Tags Untags +// @Security Bearer +// @Param id path int true "Cities 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 /cities/{id} [get] +func (_i *citiesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + citiesData, err := _i.citiesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully retrieved"}, + Data: citiesData, + }) +} + +// Save Cities +// @Summary Create Cities +// @Description API for create Cities +// @Tags Untags +// @Security Bearer +// @Param payload body request.CitiesCreateRequest true "Required payload" +// @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 /cities [post] +func (_i *citiesController) Save(c *fiber.Ctx) error { + req := new(request.CitiesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.citiesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully created"}, + }) +} + +// Update Cities +// @Summary Update Cities +// @Description API for update Cities +// @Tags Untags +// @Security Bearer +// @Body request.CitiesUpdateRequest +// @Param id path int true "Cities ID" +// @Param payload body request.CitiesUpdateRequest true "Required payload" +// @Accept json +// @Produce json +// @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 /cities/{id} [put] +func (_i *citiesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CitiesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.citiesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully updated"}, + }) +} + +// Delete Cities +// @Summary Delete Cities +// @Description API for delete Cities +// @Tags Untags +// @Security Bearer +// @Param id path int true "Cities 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 /cities/{id} [delete] +func (_i *citiesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.citiesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Cities successfully deleted"}, + }) +} diff --git a/app/module/cities/controller/controller.go b/app/module/cities/controller/controller.go new file mode 100644 index 0000000..de1eeac --- /dev/null +++ b/app/module/cities/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/cities/service" + +type Controller struct { + Cities CitiesController +} + +func NewController(CitiesService service.CitiesService) *Controller { + return &Controller{ + Cities: NewCitiesController(CitiesService), + } +} diff --git a/app/module/cities/mapper/cities.mapper.go b/app/module/cities/mapper/cities.mapper.go new file mode 100644 index 0000000..3f81675 --- /dev/null +++ b/app/module/cities/mapper/cities.mapper.go @@ -0,0 +1,17 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/cities/response" +) + +func CitiesResponseMapper(citiesReq *entity.Cities) (citiesRes *res.CitiesResponse) { + if citiesReq != nil { + citiesRes = &res.CitiesResponse{ + ID: citiesReq.ID, + CityName: citiesReq.CityName, + ProvId: citiesReq.ProvId, + } + } + return citiesRes +} diff --git a/app/module/cities/repository/cities.repository.go b/app/module/cities/repository/cities.repository.go new file mode 100644 index 0000000..5842ef0 --- /dev/null +++ b/app/module/cities/repository/cities.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/cities/request" + "campaign-pool-be/utils/paginator" +) + +type citiesRepository struct { + DB *database.Database +} + +// CitiesRepository define interface of ICitiesRepository +type CitiesRepository interface { + GetAll(req request.CitiesQueryRequest) (citiess []*entity.Cities, paging paginator.Pagination, err error) + FindOne(id uint) (cities *entity.Cities, err error) + Create(cities *entity.Cities) (err error) + Update(id uint, cities *entity.Cities) (err error) + Delete(id uint) (err error) +} + +func NewCitiesRepository(db *database.Database) CitiesRepository { + return &citiesRepository{ + DB: db, + } +} + +// implement interface of ICitiesRepository +func (_i *citiesRepository) GetAll(req request.CitiesQueryRequest) (citiess []*entity.Cities, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Cities{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&citiess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *citiesRepository) FindOne(id uint) (cities *entity.Cities, err error) { + if err := _i.DB.DB.First(&cities, id).Error; err != nil { + return nil, err + } + + return cities, nil +} + +func (_i *citiesRepository) Create(cities *entity.Cities) (err error) { + return _i.DB.DB.Create(cities).Error +} + +func (_i *citiesRepository) Update(id uint, cities *entity.Cities) (err error) { + return _i.DB.DB.Model(&entity.Cities{}). + Where(&entity.Cities{ID: id}). + Updates(cities).Error +} + +func (_i *citiesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Cities{}, id).Error +} diff --git a/app/module/cities/request/cities.request.go b/app/module/cities/request/cities.request.go new file mode 100644 index 0000000..7327a18 --- /dev/null +++ b/app/module/cities/request/cities.request.go @@ -0,0 +1,42 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" +) + +type CitiesGeneric interface { + ToEntity() +} + +type CitiesQueryRequest struct { + CityName string `json:"cityName" validate:"required"` + ProvId int `json:"provId" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CitiesCreateRequest struct { + CityName string `json:"cityName" validate:"required"` + ProvId int `json:"provId" validate:"required"` +} + +func (req CitiesCreateRequest) ToEntity() *entity.Cities { + return &entity.Cities{ + CityName: req.CityName, + ProvId: req.ProvId, + } +} + +type CitiesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + CityName string `json:"cityName" validate:"required"` + ProvId int `json:"provId" validate:"required"` +} + +func (req CitiesUpdateRequest) ToEntity() *entity.Cities { + return &entity.Cities{ + ID: req.ID, + CityName: req.CityName, + ProvId: req.ProvId, + } +} diff --git a/app/module/cities/response/cities.response.go b/app/module/cities/response/cities.response.go new file mode 100644 index 0000000..3ec3e8f --- /dev/null +++ b/app/module/cities/response/cities.response.go @@ -0,0 +1,7 @@ +package response + +type CitiesResponse struct { + ID uint `json:"id"` + CityName string `json:"cityName"` + ProvId int `json:"provId"` +} diff --git a/app/module/cities/service/cities.service.go b/app/module/cities/service/cities.service.go new file mode 100644 index 0000000..d389673 --- /dev/null +++ b/app/module/cities/service/cities.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "campaign-pool-be/app/module/cities/mapper" + "campaign-pool-be/app/module/cities/repository" + "campaign-pool-be/app/module/cities/request" + "campaign-pool-be/app/module/cities/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// CitiesService +type citiesService struct { + Repo repository.CitiesRepository + Log zerolog.Logger +} + +// CitiesService define interface of ICitiesService +type CitiesService interface { + All(req request.CitiesQueryRequest) (cities []*response.CitiesResponse, paging paginator.Pagination, err error) + Show(id uint) (cities *response.CitiesResponse, err error) + Save(req request.CitiesCreateRequest) (err error) + Update(id uint, req request.CitiesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewCitiesService init CitiesService +func NewCitiesService(repo repository.CitiesRepository, log zerolog.Logger) CitiesService { + + return &citiesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of CitiesService +func (_i *citiesService) All(req request.CitiesQueryRequest) (citiess []*response.CitiesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + citiess = append(citiess, mapper.CitiesResponseMapper(result)) + } + + return +} + +func (_i *citiesService) Show(id uint) (cities *response.CitiesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.CitiesResponseMapper(result), nil +} + +func (_i *citiesService) Save(req request.CitiesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *citiesService) Update(id uint, req request.CitiesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *citiesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/communications/mapper/communications.mapper.go b/app/module/communications/mapper/communications.mapper.go new file mode 100644 index 0000000..60ed480 --- /dev/null +++ b/app/module/communications/mapper/communications.mapper.go @@ -0,0 +1,78 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/communications/response" +) + +func ConversationsResponseMapper(conversation *entity.Conversations) *response.ConversationsResponse { + result := &response.ConversationsResponse{ + ID: conversation.ID, + Participant1ID: conversation.Participant1ID, + Participant2ID: conversation.Participant2ID, + LastMessageAt: conversation.LastMessageAt, + CreatedAt: conversation.CreatedAt, + UpdatedAt: conversation.UpdatedAt, + } + + if conversation.Participant1 != nil { + result.Participant1 = &response.UserBasicInfo{ + ID: conversation.Participant1.ID, + Username: conversation.Participant1.Username, + Fullname: conversation.Participant1.Fullname, + Email: conversation.Participant1.Email, + } + } + + if conversation.Participant2 != nil { + result.Participant2 = &response.UserBasicInfo{ + ID: conversation.Participant2.ID, + Username: conversation.Participant2.Username, + Fullname: conversation.Participant2.Fullname, + Email: conversation.Participant2.Email, + } + } + + return result +} + +func ChatMessagesResponseMapper(chatMessage *entity.ChatMessages) *response.ChatMessagesResponse { + result := &response.ChatMessagesResponse{ + ID: chatMessage.ID, + ConversationID: chatMessage.ChatSessionID, + SenderID: chatMessage.SenderID, + MessageText: &chatMessage.Message, + MessageType: chatMessage.MessageType, + FileURL: nil, // Not available in entity + FileName: nil, // Not available in entity + FileSize: nil, // Not available in entity + IsRead: false, // Not available in entity, default to false + CreatedAt: chatMessage.CreatedAt, + } + + if chatMessage.Sender != nil { + result.Sender = &response.UserBasicInfo{ + ID: chatMessage.Sender.ID, + Username: chatMessage.Sender.Username, + Fullname: chatMessage.Sender.Fullname, + Email: chatMessage.Sender.Email, + } + } + + return result +} + +func ConversationWithMessagesResponseMapper(conversation *entity.Conversations, messages []*entity.ChatMessages, unreadCount int) *response.ConversationWithMessagesResponse { + conversationResponse := ConversationsResponseMapper(conversation) + conversationResponse.UnreadCount = unreadCount + + var messagesResponse []*response.ChatMessagesResponse + for _, message := range messages { + messagesResponse = append(messagesResponse, ChatMessagesResponseMapper(message)) + } + + return &response.ConversationWithMessagesResponse{ + Conversation: conversationResponse, + Messages: messagesResponse, + } +} diff --git a/app/module/communications/repository/communications.repository.go b/app/module/communications/repository/communications.repository.go new file mode 100644 index 0000000..34388cc --- /dev/null +++ b/app/module/communications/repository/communications.repository.go @@ -0,0 +1,130 @@ +package repository + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/communications/request" + "campaign-pool-be/utils/paginator" + "time" + + "gorm.io/gorm" +) + +type communicationsRepository struct { + DB *gorm.DB +} + +type CommunicationsRepository interface { + // Conversations + GetUserConversations(userId uint, req request.ConversationsQueryRequest) (conversations []*entity.Conversations, paging paginator.Pagination, err error) + FindConversationByParticipants(participant1ID, participant2ID uint) (conversation *entity.Conversations, err error) + FindConversationByUserAndId(userId uint, conversationId uint) (conversation *entity.Conversations, err error) + CreateConversation(conversation *entity.Conversations) (result *entity.Conversations, err error) + UpdateConversationLastMessage(conversationId uint, lastMessageAt *time.Time) (err error) + + // Chat Messages + GetConversationMessages(conversationId uint, req request.ChatMessagesQueryRequest) (messages []*entity.ChatMessages, paging paginator.Pagination, err error) + CreateChatMessage(message *entity.ChatMessages) (result *entity.ChatMessages, err error) + MarkMessageAsRead(messageId uint, userId uint) (err error) + GetUnreadCount(conversationId uint, userId uint) (count int64, err error) +} + +func NewCommunicationsRepository(db *gorm.DB) CommunicationsRepository { + return &communicationsRepository{ + DB: db, + } +} + +// Conversations methods +func (_i *communicationsRepository) GetUserConversations(userId uint, req request.ConversationsQueryRequest) (conversations []*entity.Conversations, paging paginator.Pagination, err error) { + query := _i.DB.Where("participant1_id = ? OR participant2_id = ?", userId, userId) + + // Include relationships + query = query.Preload("Participant1").Preload("Participant2") + + // Order by last message time desc, then by created_at desc + query = query.Order("last_message_at DESC NULLS LAST, created_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(&conversations).Error + paging = *req.Pagination + + return +} + +func (_i *communicationsRepository) FindConversationByParticipants(participant1ID, participant2ID uint) (conversation *entity.Conversations, err error) { + err = _i.DB.Where( + "(participant1_id = ? AND participant2_id = ?) OR (participant1_id = ? AND participant2_id = ?)", + participant1ID, participant2ID, participant2ID, participant1ID).Preload("Participant1").Preload("Participant2").First(&conversation).Error + return +} + +func (_i *communicationsRepository) FindConversationByUserAndId(userId uint, conversationId uint) (conversation *entity.Conversations, err error) { + err = _i.DB.Where( + "id = ? AND (participant1_id = ? OR participant2_id = ?)", + conversationId, userId, userId).Preload("Participant1").Preload("Participant2").First(&conversation).Error + return +} + +func (_i *communicationsRepository) CreateConversation(conversation *entity.Conversations) (result *entity.Conversations, err error) { + err = _i.DB.Create(conversation).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.Preload("Participant1").Preload("Participant2").First(&result, conversation.ID).Error + return +} + +func (_i *communicationsRepository) UpdateConversationLastMessage(conversationId uint, lastMessageAt *time.Time) (err error) { + err = _i.DB.Model(&entity.Conversations{}).Where("id = ?", conversationId).Update("last_message_at", lastMessageAt).Error + return +} + +// Chat Messages methods +func (_i *communicationsRepository) GetConversationMessages(conversationId uint, req request.ChatMessagesQueryRequest) (messages []*entity.ChatMessages, paging paginator.Pagination, err error) { + query := _i.DB.Where("conversation_id = ?", conversationId) + + // Include sender relationship + query = query.Preload("Sender") + + // 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 *communicationsRepository) CreateChatMessage(message *entity.ChatMessages) (result *entity.ChatMessages, err error) { + err = _i.DB.Create(message).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.Preload("Sender").First(&result, message.ID).Error + return +} + +func (_i *communicationsRepository) MarkMessageAsRead(messageId uint, userId uint) (err error) { + err = _i.DB.Model(&entity.ChatMessages{}).Where("id = ? AND sender_id != ?", messageId, userId).Update("is_read", true).Error + return +} + +func (_i *communicationsRepository) GetUnreadCount(conversationId uint, userId uint) (count int64, err error) { + err = _i.DB.Model(&entity.ChatMessages{}).Where("conversation_id = ? AND sender_id != ? AND is_read = false", conversationId, userId).Count(&count).Error + return +} diff --git a/app/module/communications/request/communications.request.go b/app/module/communications/request/communications.request.go new file mode 100644 index 0000000..2886327 --- /dev/null +++ b/app/module/communications/request/communications.request.go @@ -0,0 +1,88 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" +) + +// Conversations Request DTOs +type ConversationsQueryRequest struct { + Pagination *paginator.Pagination `json:"pagination"` +} + +type ConversationsCreateRequest struct { + Participant2ID uint `json:"participant2Id" validate:"required"` +} + +func (req ConversationsCreateRequest) ToEntity() *entity.Conversations { + return &entity.Conversations{ + Participant1ID: 0, // Will be set in service layer + Participant2ID: req.Participant2ID, + } +} + +// Chat Messages Request DTOs +type ChatMessagesQueryRequest struct { + ChatSessionID uint `json:"chatSessionId" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ChatMessagesCreateRequest struct { + ChatSessionID uint `json:"chatSessionId" validate:"required"` + Message string `json:"message"` + MessageType string `json:"messageType" validate:"required,oneof=text image file audio"` +} + +func (req ChatMessagesCreateRequest) ToEntity() *entity.ChatMessages { + return &entity.ChatMessages{ + ChatSessionID: req.ChatSessionID, + SenderID: 0, // Will be set in service layer + Message: req.Message, + MessageType: req.MessageType, + } +} + +type ChatMessagesFileUploadRequest struct { + ChatSessionID uint `json:"chatSessionId" validate:"required"` + MessageType string `json:"messageType" validate:"required,oneof=image file audio"` + FileName string `json:"fileName"` + FileSize int64 `json:"fileSize"` +} + +func (req ChatMessagesFileUploadRequest) ToEntity(fileURL string) *entity.ChatMessages { + return &entity.ChatMessages{ + ChatSessionID: req.ChatSessionID, + SenderID: 0, // Will be set in service layer + Message: "", // File messages might not have text content + MessageType: req.MessageType, + // Note: FileURL, FileName, FileSize, IsRead fields are not available in the entity + // These would need to be stored separately or the entity needs to be updated + } +} + +type ChatMessagesMarkReadRequest struct { + MessageID uint `json:"messageId" validate:"required"` +} + +type ConversationsQueryRequestContext struct { + // No specific query parameters for conversations list +} + +func (req ConversationsQueryRequestContext) ToParamRequest() ConversationsQueryRequest { + return ConversationsQueryRequest{} +} + +type ChatMessagesQueryRequestContext struct { + ChatSessionID string `json:"chatSessionId"` +} + +func (req ChatMessagesQueryRequestContext) ToParamRequest() ChatMessagesQueryRequest { + var request ChatMessagesQueryRequest + + if chatSessionIDStr := req.ChatSessionID; chatSessionIDStr != "" { + // Parse chat session ID from string to uint + // This will be handled in the controller + } + + return request +} diff --git a/app/module/communications/response/communications.response.go b/app/module/communications/response/communications.response.go new file mode 100644 index 0000000..4bdeee6 --- /dev/null +++ b/app/module/communications/response/communications.response.go @@ -0,0 +1,56 @@ +package response + +import ( + "time" +) + +// Conversations Response DTOs +type ConversationsResponse struct { + ID uint `json:"id"` + Participant1ID uint `json:"participant1Id"` + Participant2ID uint `json:"participant2Id"` + LastMessageAt *time.Time `json:"lastMessageAt"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Nested user info + Participant1 *UserBasicInfo `json:"participant1,omitempty"` + Participant2 *UserBasicInfo `json:"participant2,omitempty"` + + // Last message preview + LastMessage *ChatMessagesResponse `json:"lastMessage,omitempty"` + + // Unread count + UnreadCount int `json:"unreadCount"` +} + +// Chat Messages Response DTOs +type ChatMessagesResponse struct { + ID uint `json:"id"` + ConversationID uint `json:"conversationId"` + SenderID uint `json:"senderId"` + MessageText *string `json:"messageText"` + MessageType string `json:"messageType"` + FileURL *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileSize *int64 `json:"fileSize"` + IsRead bool `json:"isRead"` + CreatedAt time.Time `json:"createdAt"` + + // Nested user info + Sender *UserBasicInfo `json:"sender,omitempty"` +} + +type UserBasicInfo struct { + ID uint `json:"id"` + Username string `json:"username"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} + +// Conversation with messages +type ConversationWithMessagesResponse struct { + Conversation *ConversationsResponse `json:"conversation"` + Messages []*ChatMessagesResponse `json:"messages"` + Pagination interface{} `json:"pagination"` +} diff --git a/app/module/custom_static_pages/controller/controller.go b/app/module/custom_static_pages/controller/controller.go new file mode 100644 index 0000000..de9ba21 --- /dev/null +++ b/app/module/custom_static_pages/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/custom_static_pages/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + CustomStaticPages CustomStaticPagesController +} + +func NewController(CustomStaticPagesService service.CustomStaticPagesService, log zerolog.Logger) *Controller { + return &Controller{ + CustomStaticPages: NewCustomStaticPagesController(CustomStaticPagesService, log), + } +} diff --git a/app/module/custom_static_pages/controller/custom_static_pages.controller.go b/app/module/custom_static_pages/controller/custom_static_pages.controller.go new file mode 100644 index 0000000..eafbc14 --- /dev/null +++ b/app/module/custom_static_pages/controller/custom_static_pages.controller.go @@ -0,0 +1,231 @@ +package controller + +import ( + "campaign-pool-be/app/module/custom_static_pages/request" + "campaign-pool-be/app/module/custom_static_pages/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type customStaticPagesController struct { + customStaticPagesService service.CustomStaticPagesService + Log zerolog.Logger +} + +type CustomStaticPagesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowBySlug(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewCustomStaticPagesController(customStaticPagesService service.CustomStaticPagesService, log zerolog.Logger) CustomStaticPagesController { + return &customStaticPagesController{ + customStaticPagesService: customStaticPagesService, + Log: log, + } +} + +// All get all CustomStaticPages +// @Summary Get all CustomStaticPages +// @Description API for getting all CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param req query request.CustomStaticPagesQueryRequest 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 /custom-static-pages [get] +func (_i *customStaticPagesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.CustomStaticPagesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + Slug: c.Query("slug"), + HtmlBody: c.Query("htmlBody"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Get from context + customStaticPagesData, paging, err := _i.customStaticPagesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages list successfully retrieved"}, + Data: customStaticPagesData, + Meta: paging, + }) +} + +// Show get one CustomStaticPages +// @Summary Get one CustomStaticPages +// @Description API for getting one CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param id path int true "CustomStaticPages ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/{id} [get] +func (_i *customStaticPagesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get from context + customStaticPagesData, err := _i.customStaticPagesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully retrieved"}, + Data: customStaticPagesData, + }) +} + +// ShowBySlug get one CustomStaticPages +// @Summary Get one CustomStaticPages +// @Description API for getting one CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param slug path string true "CustomStaticPages Slug" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/slug/{slug} [get] +func (_i *customStaticPagesController) ShowBySlug(c *fiber.Ctx) error { + slug := c.Params("slug") + + // Get from context + customStaticPagesData, err := _i.customStaticPagesService.ShowBySlug(slug) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully retrieved"}, + Data: customStaticPagesData, + }) +} + +// Save create CustomStaticPages +// @Summary Create CustomStaticPages +// @Description API for create CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.CustomStaticPagesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages [post] +func (_i *customStaticPagesController) Save(c *fiber.Ctx) error { + req := new(request.CustomStaticPagesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get from context + authToken := c.Get("Authorization") + dataResult, err := _i.customStaticPagesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully created"}, + Data: dataResult, + }) +} + +// Update update CustomStaticPages +// @Summary update CustomStaticPages +// @Description API for update CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.CustomStaticPagesUpdateRequest true "Required payload" +// @Param id path int true "CustomStaticPages ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/{id} [put] +func (_i *customStaticPagesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.CustomStaticPagesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + // Get from context + err = _i.customStaticPagesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully updated"}, + }) +} + +// Delete delete CustomStaticPages +// @Summary delete CustomStaticPages +// @Description API for delete CustomStaticPages +// @Tags CustomStaticPages +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "CustomStaticPages ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /custom-static-pages/{id} [delete] +func (_i *customStaticPagesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + // Get from context + err = _i.customStaticPagesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"CustomStaticPages successfully deleted"}, + }) +} diff --git a/app/module/custom_static_pages/custom_static_pages.module.go b/app/module/custom_static_pages/custom_static_pages.module.go new file mode 100644 index 0000000..1be8724 --- /dev/null +++ b/app/module/custom_static_pages/custom_static_pages.module.go @@ -0,0 +1,55 @@ +package custom_static_pages + +import ( + "campaign-pool-be/app/module/custom_static_pages/controller" + "campaign-pool-be/app/module/custom_static_pages/repository" + "campaign-pool-be/app/module/custom_static_pages/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of CustomStaticPagesRouter +type CustomStaticPagesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of CustomStaticPages module +var NewCustomStaticPagesModule = fx.Options( + // register repository of CustomStaticPages module + fx.Provide(repository.NewCustomStaticPagesRepository), + + // register service of CustomStaticPages module + fx.Provide(service.NewCustomStaticPagesService), + + // register controller of CustomStaticPages module + fx.Provide(controller.NewController), + + // register router of CustomStaticPages module + fx.Provide(NewCustomStaticPagesRouter), +) + +// init CustomStaticPagesRouter +func NewCustomStaticPagesRouter(fiber *fiber.App, controller *controller.Controller) *CustomStaticPagesRouter { + return &CustomStaticPagesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of CustomStaticPages module +func (_i *CustomStaticPagesRouter) RegisterCustomStaticPagesRoutes() { + // define controllers + customStaticPagesController := _i.Controller.CustomStaticPages + + // define routes + _i.App.Route("/custom-static-pages", func(router fiber.Router) { + router.Get("/", customStaticPagesController.All) + router.Get("/:id", customStaticPagesController.Show) + router.Get("/slug/:slug", customStaticPagesController.ShowBySlug) + router.Post("/", customStaticPagesController.Save) + router.Put("/:id", customStaticPagesController.Update) + router.Delete("/:id", customStaticPagesController.Delete) + }) +} diff --git a/app/module/custom_static_pages/mapper/custom_static_pages.mapper.go b/app/module/custom_static_pages/mapper/custom_static_pages.mapper.go new file mode 100644 index 0000000..2ba9d26 --- /dev/null +++ b/app/module/custom_static_pages/mapper/custom_static_pages.mapper.go @@ -0,0 +1,22 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/custom_static_pages/response" +) + +func CustomStaticPagesResponseMapper(customStaticPagesReq *entity.CustomStaticPages) (customStaticPagesRes *res.CustomStaticPagesResponse) { + if customStaticPagesReq != nil { + customStaticPagesRes = &res.CustomStaticPagesResponse{ + ID: customStaticPagesReq.ID, + Title: customStaticPagesReq.Title, + Description: customStaticPagesReq.Description, + Slug: customStaticPagesReq.Slug, + HtmlBody: customStaticPagesReq.HtmlBody, + IsActive: customStaticPagesReq.IsActive, + CreatedAt: customStaticPagesReq.CreatedAt, + UpdatedAt: customStaticPagesReq.UpdatedAt, + } + } + return customStaticPagesRes +} diff --git a/app/module/custom_static_pages/repository/custom_static_pages.repository.go b/app/module/custom_static_pages/repository/custom_static_pages.repository.go new file mode 100644 index 0000000..4673993 --- /dev/null +++ b/app/module/custom_static_pages/repository/custom_static_pages.repository.go @@ -0,0 +1,113 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/custom_static_pages/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type customStaticPagesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// CustomStaticPagesRepository define interface of ICustomStaticPagesRepository +type CustomStaticPagesRepository interface { + GetAll(req request.CustomStaticPagesQueryRequest) (customStaticPagess []*entity.CustomStaticPages, paging paginator.Pagination, err error) + FindOne(id uint) (customStaticPages *entity.CustomStaticPages, err error) + FindOneBySlug(slug string) (customStaticPages *entity.CustomStaticPages, err error) + Create(customStaticPages *entity.CustomStaticPages) (customStaticPagesReturn *entity.CustomStaticPages, err error) + Update(id uint, customStaticPages *entity.CustomStaticPages) (err error) + Delete(id uint) (err error) +} + +func NewCustomStaticPagesRepository(db *database.Database, logger zerolog.Logger) CustomStaticPagesRepository { + return &customStaticPagesRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of ICustomStaticPagesRepository +func (_i *customStaticPagesRepository) GetAll(req request.CustomStaticPagesQueryRequest) (customStaticPagess []*entity.CustomStaticPages, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.CustomStaticPages{}) + query = query.Where("is_active = ?", true) + query = query.Where("client_id = ?") + + 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.Slug != nil && *req.Slug != "" { + slug := strings.ToLower(*req.Slug) + query = query.Where("LOWER(slug) LIKE ?", "%"+strings.ToLower(slug)+"%") + } + 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(&customStaticPagess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *customStaticPagesRepository) FindOne(id uint) (customStaticPages *entity.CustomStaticPages, err error) { + if err := _i.DB.DB.Where("client_id = ?").First(&customStaticPages, id).Error; err != nil { + return nil, err + } + + return customStaticPages, nil +} + +func (_i *customStaticPagesRepository) FindOneBySlug(slug string) (customStaticPages *entity.CustomStaticPages, err error) { + if err := _i.DB.DB.Where("client_id = ? AND slug = ?", slug).First(&customStaticPages).Error; err != nil { + return nil, err + } + + return customStaticPages, nil +} + +func (_i *customStaticPagesRepository) Create(customStaticPages *entity.CustomStaticPages) (customStaticPagesReturn *entity.CustomStaticPages, err error) { + result := _i.DB.DB.Create(customStaticPages) + return customStaticPages, result.Error +} + +func (_i *customStaticPagesRepository) Update(id uint, customStaticPages *entity.CustomStaticPages) (err error) { + customStaticPagesMap, err := utilSvc.StructToMap(customStaticPages) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.CustomStaticPages{}). + Where(&entity.CustomStaticPages{ID: id}). + Updates(customStaticPagesMap).Error +} + +func (_i *customStaticPagesRepository) Delete(id uint) error { + return _i.DB.DB.Where("client_id = ?").Delete(&entity.CustomStaticPages{}, id).Error +} diff --git a/app/module/custom_static_pages/request/custom_static_pages.request.go b/app/module/custom_static_pages/request/custom_static_pages.request.go new file mode 100644 index 0000000..a93db3a --- /dev/null +++ b/app/module/custom_static_pages/request/custom_static_pages.request.go @@ -0,0 +1,79 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "time" +) + +type CustomStaticPagesGeneric interface { + ToEntity() +} + +type CustomStaticPagesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + Slug *string `json:"slug"` + HtmlBody *string `json:"htmlBody"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type CustomStaticPagesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description"` + Slug string `json:"slug" validate:"required"` + HtmlBody string `json:"htmlBody" validate:"required"` +} + +func (req CustomStaticPagesCreateRequest) ToEntity() *entity.CustomStaticPages { + return &entity.CustomStaticPages{ + Title: req.Title, + Description: req.Description, + Slug: req.Slug, + HtmlBody: req.HtmlBody, + IsActive: true, + } +} + +type CustomStaticPagesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description"` + Slug string `json:"slug" validate:"required"` + HtmlBody string `json:"htmlBody" validate:"required"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (req CustomStaticPagesUpdateRequest) ToEntity() *entity.CustomStaticPages { + return &entity.CustomStaticPages{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + Slug: req.Slug, + HtmlBody: req.HtmlBody, + UpdatedAt: req.UpdatedAt, + } +} + +type CustomStaticPagesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + Slug string `json:"slug"` + HtmlBody string `json:"htmlBody"` +} + +func (req CustomStaticPagesQueryRequestContext) ToParamRequest() CustomStaticPagesQueryRequest { + var request CustomStaticPagesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if slug := req.Slug; slug != "" { + request.Slug = &slug + } + + return request +} diff --git a/app/module/custom_static_pages/response/custom_static_pages.response.go b/app/module/custom_static_pages/response/custom_static_pages.response.go new file mode 100644 index 0000000..89161f9 --- /dev/null +++ b/app/module/custom_static_pages/response/custom_static_pages.response.go @@ -0,0 +1,14 @@ +package response + +import "time" + +type CustomStaticPagesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Slug string `json:"slug"` + HtmlBody string `json:"htmlBody"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/custom_static_pages/service/custom_static_pages.service.go b/app/module/custom_static_pages/service/custom_static_pages.service.go new file mode 100644 index 0000000..97ddb31 --- /dev/null +++ b/app/module/custom_static_pages/service/custom_static_pages.service.go @@ -0,0 +1,98 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/custom_static_pages/mapper" + "campaign-pool-be/app/module/custom_static_pages/repository" + "campaign-pool-be/app/module/custom_static_pages/request" + "campaign-pool-be/app/module/custom_static_pages/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// CustomStaticPagesService +type customStaticPagesService struct { + Repo repository.CustomStaticPagesRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// CustomStaticPagesService define interface of ICustomStaticPagesService +type CustomStaticPagesService interface { + All(req request.CustomStaticPagesQueryRequest) (customStaticPages []*response.CustomStaticPagesResponse, paging paginator.Pagination, err error) + Show(id uint) (customStaticPages *response.CustomStaticPagesResponse, err error) + ShowBySlug(slug string) (customStaticPages *response.CustomStaticPagesResponse, err error) + Save(req request.CustomStaticPagesCreateRequest, authToken string) (customStaticPages *entity.CustomStaticPages, err error) + Update(id uint, req request.CustomStaticPagesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewCustomStaticPagesService init CustomStaticPagesService +func NewCustomStaticPagesService(repo repository.CustomStaticPagesRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) CustomStaticPagesService { + + return &customStaticPagesService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of CustomStaticPagesService +func (_i *customStaticPagesService) All(req request.CustomStaticPagesQueryRequest) (customStaticPagess []*response.CustomStaticPagesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + customStaticPagess = append(customStaticPagess, mapper.CustomStaticPagesResponseMapper(result)) + } + + return +} + +func (_i *customStaticPagesService) Show(id uint) (customStaticPages *response.CustomStaticPagesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.CustomStaticPagesResponseMapper(result), nil +} + +func (_i *customStaticPagesService) ShowBySlug(slug string) (customStaticPages *response.CustomStaticPagesResponse, err error) { + result, err := _i.Repo.FindOneBySlug(slug) + if err != nil { + return nil, err + } + + return mapper.CustomStaticPagesResponseMapper(result), nil +} + +func (_i *customStaticPagesService) Save(req request.CustomStaticPagesCreateRequest, authToken string) (customStaticPages *entity.CustomStaticPages, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + + //createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + //newReq.CreatedById = &createdBy.ID + + return _i.Repo.Create(newReq) +} + +func (_i *customStaticPagesService) Update(id uint, req request.CustomStaticPagesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *customStaticPagesService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(id, result) +} diff --git a/app/module/districts/controller/controller.go b/app/module/districts/controller/controller.go new file mode 100644 index 0000000..d68d25d --- /dev/null +++ b/app/module/districts/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/districts/service" + +type Controller struct { + Districts DistrictsController +} + +func NewController(DistrictsService service.DistrictsService) *Controller { + return &Controller{ + Districts: NewDistrictsController(DistrictsService), + } +} diff --git a/app/module/districts/controller/districts.controller.go b/app/module/districts/controller/districts.controller.go new file mode 100644 index 0000000..f983ca3 --- /dev/null +++ b/app/module/districts/controller/districts.controller.go @@ -0,0 +1,182 @@ +package controller + +import ( + "campaign-pool-be/app/module/districts/request" + "campaign-pool-be/app/module/districts/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type districtsController struct { + districtsService service.DistrictsService +} + +type DistrictsController 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 NewDistrictsController(districtsService service.DistrictsService) DistrictsController { + return &districtsController{ + districtsService: districtsService, + } +} + +// All Districts +// @Summary Get all Districts +// @Description API for getting all Districts +// @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 /districts [get] +func (_i *districtsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.DistrictsQueryRequest + req.Pagination = paginate + + districtsData, paging, err := _i.districtsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts list successfully retrieved"}, + Data: districtsData, + Meta: paging, + }) +} + +// Show Districts +// @Summary Get one Districts +// @Description API for getting one Districts +// @Tags Untags +// @Security Bearer +// @Param id path int true "Districts 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 /districts/{id} [get] +func (_i *districtsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + districtsData, err := _i.districtsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully retrieved"}, + Data: districtsData, + }) +} + +// Save Districts +// @Summary Create Districts +// @Description API for create Districts +// @Tags Untags +// @Security Bearer +// @Body request.DistrictsCreateRequest +// @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 /districts [post] +func (_i *districtsController) Save(c *fiber.Ctx) error { + req := new(request.DistrictsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.districtsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully created"}, + }) +} + +// Update Districts +// @Summary Update Districts +// @Description API for update Districts +// @Tags Untags +// @Security Bearer +// @Body request.DistrictsUpdateRequest +// @Param id path int true "Districts 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 /districts/{id} [put] +func (_i *districtsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.DistrictsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.districtsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully updated"}, + }) +} + +// Delete Districts +// @Summary Delete Districts +// @Description API for delete Districts +// @Tags Untags +// @Security Bearer +// @Param id path int true "Districts 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 /districts/{id} [delete] +func (_i *districtsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.districtsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Districts successfully deleted"}, + }) +} diff --git a/app/module/districts/districts.module.go b/app/module/districts/districts.module.go new file mode 100644 index 0000000..d72e51e --- /dev/null +++ b/app/module/districts/districts.module.go @@ -0,0 +1,54 @@ +package districts + +import ( + "campaign-pool-be/app/module/districts/controller" + "campaign-pool-be/app/module/districts/repository" + "campaign-pool-be/app/module/districts/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// DistrictsRouter struct of DistrictsRouter +type DistrictsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// NewDistrictsModule register bulky of Districts module +var NewDistrictsModule = fx.Options( + // register repository of Districts module + fx.Provide(repository.NewDistrictsRepository), + + // register service of Districts module + fx.Provide(service.NewDistrictsService), + + // register controller of Districts module + fx.Provide(controller.NewController), + + // register router of Districts module + fx.Provide(NewDistrictsRouter), +) + +// NewDistrictsRouter init DistrictsRouter +func NewDistrictsRouter(fiber *fiber.App, controller *controller.Controller) *DistrictsRouter { + return &DistrictsRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterDistrictsRoutes register routes of Districts module +func (_i *DistrictsRouter) RegisterDistrictsRoutes() { + // define controllers + districtsController := _i.Controller.Districts + + // define routes + _i.App.Route("/districts", func(router fiber.Router) { + router.Get("/", districtsController.All) + router.Get("/:id", districtsController.Show) + router.Post("/", districtsController.Save) + router.Put("/:id", districtsController.Update) + router.Delete("/:id", districtsController.Delete) + }) +} diff --git a/app/module/districts/mapper/districts.mapper.go b/app/module/districts/mapper/districts.mapper.go new file mode 100644 index 0000000..d50a0b5 --- /dev/null +++ b/app/module/districts/mapper/districts.mapper.go @@ -0,0 +1,17 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/districts/response" +) + +func DistrictsResponseMapper(districtsReq *entity.Districts) (districtsRes *res.DistrictsResponse) { + if districtsReq != nil { + districtsRes = &res.DistrictsResponse{ + ID: districtsReq.ID, + DisNam: districtsReq.DisName, + CityId: districtsReq.CityId, + } + } + return districtsRes +} diff --git a/app/module/districts/repository/districts.repository.go b/app/module/districts/repository/districts.repository.go new file mode 100644 index 0000000..afe8141 --- /dev/null +++ b/app/module/districts/repository/districts.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/districts/request" + "campaign-pool-be/utils/paginator" +) + +type districtsRepository struct { + DB *database.Database +} + +// DistrictsRepository define interface of IDistrictsRepository +type DistrictsRepository interface { + GetAll(req request.DistrictsQueryRequest) (districtss []*entity.Districts, paging paginator.Pagination, err error) + FindOne(id uint) (districts *entity.Districts, err error) + Create(districts *entity.Districts) (err error) + Update(id uint, districts *entity.Districts) (err error) + Delete(id uint) (err error) +} + +func NewDistrictsRepository(db *database.Database) DistrictsRepository { + return &districtsRepository{ + DB: db, + } +} + +// implement interface of IDistrictsRepository +func (_i *districtsRepository) GetAll(req request.DistrictsQueryRequest) (districtss []*entity.Districts, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Districts{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&districtss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *districtsRepository) FindOne(id uint) (districts *entity.Districts, err error) { + if err := _i.DB.DB.First(&districts, id).Error; err != nil { + return nil, err + } + + return districts, nil +} + +func (_i *districtsRepository) Create(districts *entity.Districts) (err error) { + return _i.DB.DB.Create(districts).Error +} + +func (_i *districtsRepository) Update(id uint, districts *entity.Districts) (err error) { + return _i.DB.DB.Model(&entity.Districts{}). + Where(&entity.Districts{ID: id}). + Updates(districts).Error +} + +func (_i *districtsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Districts{}, id).Error +} diff --git a/app/module/districts/request/districts.request.go b/app/module/districts/request/districts.request.go new file mode 100644 index 0000000..975e73c --- /dev/null +++ b/app/module/districts/request/districts.request.go @@ -0,0 +1,42 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" +) + +type DistrictsGeneric interface { + ToEntity() +} + +type DistrictsQueryRequest struct { + DisName string `json:"disName" validate:"required"` + CityId int `json:"cityId" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type DistrictsCreateRequest struct { + DisName string `json:"disName" validate:"required"` + CityId int `json:"cityId" validate:"required"` +} + +func (req DistrictsCreateRequest) ToEntity() *entity.Districts { + return &entity.Districts{ + DisName: req.DisName, + CityId: req.CityId, + } +} + +type DistrictsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + DisName string `json:"disName" validate:"required"` + CityId int `json:"cityId" validate:"required"` +} + +func (req DistrictsUpdateRequest) ToEntity() *entity.Districts { + return &entity.Districts{ + ID: req.ID, + DisName: req.DisName, + CityId: req.CityId, + } +} diff --git a/app/module/districts/response/districts.response.go b/app/module/districts/response/districts.response.go new file mode 100644 index 0000000..f4b1329 --- /dev/null +++ b/app/module/districts/response/districts.response.go @@ -0,0 +1,7 @@ +package response + +type DistrictsResponse struct { + ID uint `json:"id"` + DisNam string `json:"disNam"` + CityId int `json:"cityId"` +} diff --git a/app/module/districts/service/districts.service.go b/app/module/districts/service/districts.service.go new file mode 100644 index 0000000..4d90322 --- /dev/null +++ b/app/module/districts/service/districts.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "campaign-pool-be/app/module/districts/mapper" + "campaign-pool-be/app/module/districts/repository" + "campaign-pool-be/app/module/districts/request" + "campaign-pool-be/app/module/districts/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// DistrictsService +type districtsService struct { + Repo repository.DistrictsRepository + Log zerolog.Logger +} + +// DistrictsService define interface of IDistrictsService +type DistrictsService interface { + All(req request.DistrictsQueryRequest) (districts []*response.DistrictsResponse, paging paginator.Pagination, err error) + Show(id uint) (districts *response.DistrictsResponse, err error) + Save(req request.DistrictsCreateRequest) (err error) + Update(id uint, req request.DistrictsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewDistrictsService init DistrictsService +func NewDistrictsService(repo repository.DistrictsRepository, log zerolog.Logger) DistrictsService { + + return &districtsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of DistrictsService +func (_i *districtsService) All(req request.DistrictsQueryRequest) (districtss []*response.DistrictsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + districtss = append(districtss, mapper.DistrictsResponseMapper(result)) + } + + return +} + +func (_i *districtsService) Show(id uint) (districts *response.DistrictsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.DistrictsResponseMapper(result), nil +} + +func (_i *districtsService) Save(req request.DistrictsCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *districtsService) Update(id uint, req request.DistrictsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *districtsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/ebooks/controller/ebook_purchases.controller.go b/app/module/ebooks/controller/ebook_purchases.controller.go new file mode 100644 index 0000000..c2eb17d --- /dev/null +++ b/app/module/ebooks/controller/ebook_purchases.controller.go @@ -0,0 +1,210 @@ +package controller + +import ( + "campaign-pool-be/app/module/ebooks/service" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type ebookPurchasesController struct { + purchaseService service.EbookPurchasesService + usersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type EbookPurchasesController interface { + GetByBuyerId(c *fiber.Ctx) error + PurchaseEbook(c *fiber.Ctx) error + UpdatePaymentStatus(c *fiber.Ctx) error + GetPurchaseById(c *fiber.Ctx) error + ConfirmPayment(c *fiber.Ctx) error +} + +func NewEbookPurchasesController(purchaseService service.EbookPurchasesService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) EbookPurchasesController { + return &ebookPurchasesController{ + purchaseService: purchaseService, + usersRepo: usersRepo, + Log: log, + } +} + +// GetByBuyerId Purchase +// @Summary Get user purchases +// @Description API for getting user purchases +// @Tags Ebook Purchase +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @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 /ebooks/purchases [get] +func (_i *ebookPurchasesController) GetByBuyerId(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + purchasesData, paging, err := _i.purchaseService.GetByBuyerId(authToken, paginate) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Purchases successfully retrieved"}, + Data: purchasesData, + Meta: paging, + }) +} + +// PurchaseEbook Purchase +// @Summary Purchase ebook +// @Description API for purchasing ebook +// @Tags Ebook Purchase +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param ebookId path int true "Ebook ID" +// @Param paymentMethod query string true "Payment Method" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/purchase/{ebookId} [post] +func (_i *ebookPurchasesController) PurchaseEbook(c *fiber.Ctx) error { + ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0) + if err != nil { + return err + } + + paymentMethod := c.Query("paymentMethod") + if paymentMethod == "" { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"Payment method is required"}, + }) + } + + authToken := c.Get("Authorization") + + purchase, err := _i.purchaseService.PurchaseEbook(authToken, uint(ebookId), paymentMethod) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook purchase successfully created"}, + Data: purchase, + }) +} + +// UpdatePaymentStatus Purchase +// @Summary Update payment status +// @Description API for updating payment status +// @Tags Ebook Purchase +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Purchase ID" +// @Param payload body PaymentStatusUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/purchases/{id}/payment [put] +func (_i *ebookPurchasesController) UpdatePaymentStatus(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(PaymentStatusUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.purchaseService.UpdatePaymentStatus(uint(id), req.PaymentStatus, req.TransactionId, req.PaymentProof) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Payment status successfully updated"}, + }) +} + +// GetPurchaseById Purchase +// @Summary Get purchase by ID +// @Description API for getting purchase by ID +// @Tags Ebook Purchase +// @Security Bearer +// @Param id path int true "Purchase ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/purchases/{id} [get] +func (_i *ebookPurchasesController) GetPurchaseById(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + purchaseData, err := _i.purchaseService.GetPurchaseById(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Purchase successfully retrieved"}, + Data: purchaseData, + }) +} + +// ConfirmPayment Purchase +// @Summary Confirm payment +// @Description API for confirming payment +// @Tags Ebook Purchase +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Purchase ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/purchases/{id}/confirm [put] +func (_i *ebookPurchasesController) ConfirmPayment(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.purchaseService.ConfirmPayment(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Payment successfully confirmed"}, + }) +} + +// PaymentStatusUpdateRequest struct for payment status update +type PaymentStatusUpdateRequest struct { + PaymentStatus string `json:"paymentStatus" validate:"required"` + TransactionId string `json:"transactionId"` + PaymentProof string `json:"paymentProof"` +} diff --git a/app/module/ebooks/controller/ebook_ratings.controller.go b/app/module/ebooks/controller/ebook_ratings.controller.go new file mode 100644 index 0000000..6cf4fce --- /dev/null +++ b/app/module/ebooks/controller/ebook_ratings.controller.go @@ -0,0 +1,313 @@ +package controller + +import ( + "campaign-pool-be/app/module/ebooks/request" + "campaign-pool-be/app/module/ebooks/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type ebookRatingsController struct { + ratingsService service.EbookRatingsService + Log zerolog.Logger +} + +type EbookRatingsController interface { + GetAll(c *fiber.Ctx) error + GetByEbookId(c *fiber.Ctx) error + GetByUserId(c *fiber.Ctx) error + GetEbookRatingSummary(c *fiber.Ctx) error + Create(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + GetRatingStats(c *fiber.Ctx) error +} + +func NewEbookRatingsController(ratingsService service.EbookRatingsService, log zerolog.Logger) EbookRatingsController { + return &ebookRatingsController{ + ratingsService: ratingsService, + Log: log, + } +} + +// GetAll Ratings +// @Summary Get all Ebook Ratings +// @Description API for getting all Ebook Ratings +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.EbookRatingsQueryRequest 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 /ebook-ratings [get] +func (_i *ebookRatingsController) GetAll(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.EbookRatingsQueryRequestContext{ + EbookId: c.Query("ebookId"), + UserId: c.Query("userId"), + Rating: c.Query("rating"), + IsVerified: c.Query("isVerified"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + ratingsData, paging, err := _i.ratingsService.GetAll(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook ratings list successfully retrieved"}, + Data: ratingsData, + Meta: paging, + }) +} + +// GetByEbookId Ratings +// @Summary Get ratings by ebook ID +// @Description API for getting ratings by ebook ID +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Ebook ID" +// @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 /ebook-ratings/ebook/{id} [get] +func (_i *ebookRatingsController) GetByEbookId(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + ratingsData, paging, err := _i.ratingsService.GetByEbookId(uint(id), paginate) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook ratings successfully retrieved"}, + Data: ratingsData, + Meta: paging, + }) +} + +// GetByUserId Ratings +// @Summary Get user ratings +// @Description API for getting user ratings +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @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 /ebook-ratings/user [get] +func (_i *ebookRatingsController) GetByUserId(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + ratingsData, paging, err := _i.ratingsService.GetByUserId(authToken, paginate) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"User ratings successfully retrieved"}, + Data: ratingsData, + Meta: paging, + }) +} + +// GetEbookRatingSummary Ratings +// @Summary Get ebook rating summary +// @Description API for getting ebook rating summary with stats and recent reviews +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebook-ratings/summary/{id} [get] +func (_i *ebookRatingsController) GetEbookRatingSummary(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + summaryData, err := _i.ratingsService.GetEbookRatingSummary(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook rating summary successfully retrieved"}, + Data: summaryData, + }) +} + +// Create Rating +// @Summary Create ebook rating +// @Description API for creating ebook rating +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.EbookRatingsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebook-ratings [post] +func (_i *ebookRatingsController) Create(c *fiber.Ctx) error { + req := new(request.EbookRatingsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.ratingsService.Create(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook rating successfully created"}, + Data: dataResult, + }) +} + +// Update Rating +// @Summary Update ebook rating +// @Description API for updating ebook rating +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.EbookRatingsUpdateRequest true "Required payload" +// @Param id path int true "Rating ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebook-ratings/{id} [put] +func (_i *ebookRatingsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.EbookRatingsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.ratingsService.Update(uint(id), *req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook rating successfully updated"}, + }) +} + +// Delete Rating +// @Summary Delete ebook rating +// @Description API for deleting ebook rating +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Rating ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebook-ratings/{id} [delete] +func (_i *ebookRatingsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.ratingsService.Delete(uint(id), authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook rating successfully deleted"}, + }) +} + +// GetRatingStats Ratings +// @Summary Get ebook rating statistics +// @Description API for getting ebook rating statistics +// @Tags Ebook Ratings +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebook-ratings/stats/{id} [get] +func (_i *ebookRatingsController) GetRatingStats(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + statsData, err := _i.ratingsService.GetRatingStats(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook rating statistics successfully retrieved"}, + Data: statsData, + }) +} diff --git a/app/module/ebooks/controller/ebook_wishlists.controller.go b/app/module/ebooks/controller/ebook_wishlists.controller.go new file mode 100644 index 0000000..7f10476 --- /dev/null +++ b/app/module/ebooks/controller/ebook_wishlists.controller.go @@ -0,0 +1,160 @@ +package controller + +import ( + "campaign-pool-be/app/module/ebooks/service" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" +) + +type ebookWishlistsController struct { + wishlistService service.EbookWishlistsService + usersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type EbookWishlistsController interface { + GetByUserId(c *fiber.Ctx) error + AddToWishlist(c *fiber.Ctx) error + RemoveFromWishlist(c *fiber.Ctx) error + IsInWishlist(c *fiber.Ctx) error +} + +func NewEbookWishlistsController(wishlistService service.EbookWishlistsService, usersRepo usersRepository.UsersRepository, log zerolog.Logger) EbookWishlistsController { + return &ebookWishlistsController{ + wishlistService: wishlistService, + usersRepo: usersRepo, + Log: log, + } +} + +// GetByUserId Wishlist +// @Summary Get user wishlist +// @Description API for getting user wishlist +// @Tags Ebook Wishlist +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @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 /ebooks/wishlist [get] +func (_i *ebookWishlistsController) GetByUserId(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + wishlistsData, paging, err := _i.wishlistService.GetByUserId(authToken, paginate) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Wishlist successfully retrieved"}, + Data: wishlistsData, + Meta: paging, + }) +} + +// AddToWishlist Wishlist +// @Summary Add ebook to wishlist +// @Description API for adding ebook to wishlist +// @Tags Ebook Wishlist +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param ebookId path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/wishlist/{ebookId} [post] +func (_i *ebookWishlistsController) AddToWishlist(c *fiber.Ctx) error { + ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.wishlistService.AddToWishlist(authToken, uint(ebookId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully added to wishlist"}, + }) +} + +// RemoveFromWishlist Wishlist +// @Summary Remove ebook from wishlist +// @Description API for removing ebook from wishlist +// @Tags Ebook Wishlist +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param ebookId path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/wishlist/{ebookId} [delete] +func (_i *ebookWishlistsController) RemoveFromWishlist(c *fiber.Ctx) error { + ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + err = _i.wishlistService.RemoveFromWishlist(authToken, uint(ebookId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully removed from wishlist"}, + }) +} + +// IsInWishlist Wishlist +// @Summary Check if ebook is in wishlist +// @Description API for checking if ebook is in wishlist +// @Tags Ebook Wishlist +// @Security Bearer +// @Param ebookId path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/wishlist/check/{ebookId} [get] +func (_i *ebookWishlistsController) IsInWishlist(c *fiber.Ctx) error { + ebookId, err := strconv.ParseUint(c.Params("ebookId"), 10, 0) + if err != nil { + return err + } + + authToken := c.Get("Authorization") + + isInWishlist, err := _i.wishlistService.IsInWishlist(authToken, uint(ebookId)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Wishlist status successfully retrieved"}, + Data: map[string]bool{"isInWishlist": isInWishlist}, + }) +} diff --git a/app/module/ebooks/controller/ebooks.controller.go b/app/module/ebooks/controller/ebooks.controller.go new file mode 100644 index 0000000..e1a97c4 --- /dev/null +++ b/app/module/ebooks/controller/ebooks.controller.go @@ -0,0 +1,350 @@ +package controller + +import ( + "campaign-pool-be/app/module/ebooks/request" + "campaign-pool-be/app/module/ebooks/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type ebooksController struct { + ebooksService service.EbooksService + Log zerolog.Logger +} + +type EbooksController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowBySlug(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + SavePdfFile(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + SummaryStats(c *fiber.Ctx) error + DownloadPdf(c *fiber.Ctx) error +} + +func NewEbooksController(ebooksService service.EbooksService, log zerolog.Logger) EbooksController { + return &ebooksController{ + ebooksService: ebooksService, + Log: log, + } +} + +// All Ebooks +// @Summary Get all Ebooks +// @Description API for getting all Ebooks +// @Tags Ebooks +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param req query request.EbooksQueryRequest 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 /ebooks [get] +func (_i *ebooksController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.EbooksQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + AuthorId: c.Query("authorId"), + Category: c.Query("category"), + Tags: c.Query("tags"), + MinPrice: c.Query("minPrice"), + MaxPrice: c.Query("maxPrice"), + StatusId: c.Query("statusId"), + IsPublished: c.Query("isPublished"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + ebooksData, paging, err := _i.ebooksService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebooks list successfully retrieved"}, + Data: ebooksData, + Meta: paging, + }) +} + +// Show Ebook +// @Summary Get one Ebook +// @Description API for getting one Ebook +// @Tags Ebooks +// @Security Bearer +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/{id} [get] +func (_i *ebooksController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + ebookData, err := _i.ebooksService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully retrieved"}, + Data: ebookData, + }) +} + +// ShowBySlug Ebook +// @Summary Get one Ebook by slug +// @Description API for getting one Ebook by slug +// @Tags Ebooks +// @Security Bearer +// @Param slug path string true "Ebook Slug" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/slug/{slug} [get] +func (_i *ebooksController) ShowBySlug(c *fiber.Ctx) error { + slug := c.Params("slug") + + ebookData, err := _i.ebooksService.ShowBySlug(slug) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully retrieved"}, + Data: ebookData, + }) +} + +// Save Ebook +// @Summary Create Ebook +// @Description API for create Ebook +// @Tags Ebooks +// @Security Bearer +// @Param X-Csrf-Token header string false "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.EbooksCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks [post] +func (_i *ebooksController) Save(c *fiber.Ctx) error { + req := new(request.EbooksCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.ebooksService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully created"}, + Data: dataResult, + }) +} + +// SavePdfFile Ebook +// @Summary Save PDF File Ebook +// @Description API for Save PDF File of Ebook +// @Tags Ebooks +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param file formData file true "Upload PDF file" +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/pdf/{id} [post] +func (_i *ebooksController) SavePdfFile(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.ebooksService.SavePdfFile(c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"PDF file of Ebook successfully uploaded"}, + }) +} + +// SaveThumbnail Ebook +// @Summary Save Thumbnail Ebook +// @Description API for Save Thumbnail of Ebook +// @Tags Ebooks +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param files formData file true "Upload thumbnail" +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/thumbnail/{id} [post] +func (_i *ebooksController) SaveThumbnail(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.ebooksService.SaveThumbnail(c, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of Ebook successfully uploaded"}, + }) +} + +// Update Ebook +// @Summary Update Ebook +// @Description API for update Ebook +// @Tags Ebooks +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.EbooksUpdateRequest true "Required payload" +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/{id} [put] +func (_i *ebooksController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.EbooksUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.ebooksService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully updated"}, + }) +} + +// Delete Ebook +// @Summary Delete Ebook +// @Description API for delete Ebook +// @Tags Ebooks +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/{id} [delete] +func (_i *ebooksController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.ebooksService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Ebook successfully deleted"}, + }) +} + +// SummaryStats Ebook +// @Summary SummaryStats Ebook +// @Description API for Summary Stats of Ebook +// @Tags Ebooks +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/statistic/summary [get] +func (_i *ebooksController) SummaryStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + response, err := _i.ebooksService.SummaryStats(authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Summary Stats of Ebooks successfully retrieved"}, + Data: response, + }) +} + +// DownloadPdf Ebook +// @Summary Download PDF Ebook +// @Description API for Download PDF of Ebook +// @Tags Ebooks +// @Security Bearer +// @Param id path int true "Ebook ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /ebooks/download/{id} [get] +func (_i *ebooksController) DownloadPdf(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.ebooksService.DownloadPdf(c, uint(id)) + if err != nil { + return err + } + + return nil +} diff --git a/app/module/ebooks/ebooks.module.go b/app/module/ebooks/ebooks.module.go new file mode 100644 index 0000000..7e3cc94 --- /dev/null +++ b/app/module/ebooks/ebooks.module.go @@ -0,0 +1,125 @@ +package ebooks + +import ( + "campaign-pool-be/app/module/ebooks/controller" + "campaign-pool-be/app/module/ebooks/repository" + "campaign-pool-be/app/module/ebooks/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// EbooksRouter struct of EbooksRouter +type EbooksRouter struct { + App fiber.Router + Controller *Controller +} + +// Controller struct for all controllers +type Controller struct { + Ebooks controller.EbooksController + Wishlists controller.EbookWishlistsController + Purchases controller.EbookPurchasesController + Ratings controller.EbookRatingsController +} + +// NewEbooksModule register bulky of Ebooks module +var NewEbooksModule = fx.Options( + // register repository of Ebooks module + fx.Provide(repository.NewEbooksRepository), + fx.Provide(repository.NewEbookWishlistsRepository), + fx.Provide(repository.NewEbookPurchasesRepository), + fx.Provide(repository.NewEbookRatingsRepository), + + // register service of Ebooks module + fx.Provide(service.NewEbooksService), + fx.Provide(service.NewEbookWishlistsService), + fx.Provide(service.NewEbookPurchasesService), + fx.Provide(service.NewEbookRatingsService), + + // register controller of Ebooks module + fx.Provide(controller.NewEbooksController), + fx.Provide(controller.NewEbookWishlistsController), + fx.Provide(controller.NewEbookPurchasesController), + fx.Provide(controller.NewEbookRatingsController), + fx.Provide(NewController), + + // register router of Ebooks module + fx.Provide(NewEbooksRouter), +) + +// NewController init Controller +func NewController( + ebooksController controller.EbooksController, + wishlistsController controller.EbookWishlistsController, + purchasesController controller.EbookPurchasesController, + ratingsController controller.EbookRatingsController, +) *Controller { + return &Controller{ + Ebooks: ebooksController, + Wishlists: wishlistsController, + Purchases: purchasesController, + Ratings: ratingsController, + } +} + +// NewEbooksRouter init EbooksRouter +func NewEbooksRouter(fiber *fiber.App, controller *Controller) *EbooksRouter { + return &EbooksRouter{ + App: fiber, + Controller: controller, + } +} + +// RegisterEbooksRoutes register routes of Ebooks module +func (_i *EbooksRouter) RegisterEbooksRoutes() { + // define controllers + ebooksController := _i.Controller.Ebooks + wishlistsController := _i.Controller.Wishlists + purchasesController := _i.Controller.Purchases + ratingsController := _i.Controller.Ratings + + // define routes + _i.App.Route("/ebooks", func(router fiber.Router) { + // Ebook CRUD routes + router.Get("/", ebooksController.All) + router.Get("/slug/:slug", ebooksController.ShowBySlug) + router.Get("/:id", ebooksController.Show) + router.Post("/", ebooksController.Save) + router.Put("/:id", ebooksController.Update) + router.Delete("/:id", ebooksController.Delete) + + // File upload routes + router.Post("/pdf/:id", ebooksController.SavePdfFile) + router.Post("/thumbnail/:id", ebooksController.SaveThumbnail) + + // Download route + router.Get("/download/:id", ebooksController.DownloadPdf) + + // Statistics route + router.Get("/statistic/summary", ebooksController.SummaryStats) + + // Wishlist routes + router.Get("/wishlist", wishlistsController.GetByUserId) + router.Post("/wishlist/:ebookId", wishlistsController.AddToWishlist) + router.Delete("/wishlist/:ebookId", wishlistsController.RemoveFromWishlist) + router.Get("/wishlist/check/:ebookId", wishlistsController.IsInWishlist) + + // Purchase routes + router.Get("/purchases", purchasesController.GetByBuyerId) + router.Post("/purchase/:ebookId", purchasesController.PurchaseEbook) + router.Get("/purchases/:id", purchasesController.GetPurchaseById) + router.Put("/purchases/:id/payment", purchasesController.UpdatePaymentStatus) + router.Put("/purchases/:id/confirm", purchasesController.ConfirmPayment) + + // Rating routes + router.Get("/ratings", ratingsController.GetAll) + router.Post("/ratings", ratingsController.Create) + router.Put("/ratings/:id", ratingsController.Update) + router.Delete("/ratings/:id", ratingsController.Delete) + router.Get("/ratings/ebook/:id", ratingsController.GetByEbookId) + router.Get("/ratings/user", ratingsController.GetByUserId) + router.Get("/ratings/summary/:id", ratingsController.GetEbookRatingSummary) + router.Get("/ratings/stats/:id", ratingsController.GetRatingStats) + }) +} diff --git a/app/module/ebooks/mapper/ebook_ratings.mapper.go b/app/module/ebooks/mapper/ebook_ratings.mapper.go new file mode 100644 index 0000000..58e9f66 --- /dev/null +++ b/app/module/ebooks/mapper/ebook_ratings.mapper.go @@ -0,0 +1,94 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + ebookRatingsResponse "campaign-pool-be/app/module/ebooks/response" +) + +func ToEbookRatingsResponse(rating *entity.EbookRatings) *ebookRatingsResponse.EbookRatingsResponse { + if rating == nil { + return nil + } + + var userName, userEmail *string + if rating.User != nil { + if rating.IsAnonymous != nil && *rating.IsAnonymous { + userName = stringPtr("Anonymous") + userEmail = nil + } else { + userName = &rating.User.Fullname + userEmail = &rating.User.Email + } + } + + var ebookTitle *string + if rating.Ebook != nil { + ebookTitle = &rating.Ebook.Title + } + + return &ebookRatingsResponse.EbookRatingsResponse{ + ID: rating.ID, + UserId: rating.UserId, + UserName: userName, + UserEmail: userEmail, + EbookId: rating.EbookId, + EbookTitle: ebookTitle, + PurchaseId: rating.PurchaseId, + Rating: rating.Rating, + Review: rating.Review, + IsAnonymous: rating.IsAnonymous, + IsVerified: rating.IsVerified, + StatusId: rating.StatusId, + IsActive: rating.IsActive, + CreatedAt: rating.CreatedAt, + UpdatedAt: rating.UpdatedAt, + } +} + +func ToEbookRatingsResponseList(ratings []*entity.EbookRatings) []*ebookRatingsResponse.EbookRatingsResponse { + if ratings == nil { + return nil + } + + var responses []*ebookRatingsResponse.EbookRatingsResponse + for _, rating := range ratings { + responses = append(responses, ToEbookRatingsResponse(rating)) + } + + return responses +} + +func ToEbookRatingSummaryResponse(ebookId uint, ebookTitle string, averageRating float64, totalRatings int, ratingCounts map[int]int, recentReviews []*entity.EbookRatings) *ebookRatingsResponse.EbookRatingSummaryResponse { + reviews := ToEbookRatingsResponseList(recentReviews) + var reviewsList []ebookRatingsResponse.EbookRatingsResponse + for _, review := range reviews { + reviewsList = append(reviewsList, *review) + } + + return &ebookRatingsResponse.EbookRatingSummaryResponse{ + EbookId: ebookId, + EbookTitle: ebookTitle, + AverageRating: averageRating, + TotalRatings: totalRatings, + RatingCounts: ratingCounts, + RecentReviews: reviewsList, + } +} + +func ToEbookRatingStatsResponse(totalRatings int, averageRating float64, ratingCounts map[int]int) *ebookRatingsResponse.EbookRatingStatsResponse { + return &ebookRatingsResponse.EbookRatingStatsResponse{ + TotalRatings: totalRatings, + AverageRating: averageRating, + RatingCounts: ratingCounts, + FiveStarCount: ratingCounts[5], + FourStarCount: ratingCounts[4], + ThreeStarCount: ratingCounts[3], + TwoStarCount: ratingCounts[2], + OneStarCount: ratingCounts[1], + } +} + +// Helper function +func stringPtr(s string) *string { + return &s +} diff --git a/app/module/ebooks/mapper/ebooks.mapper.go b/app/module/ebooks/mapper/ebooks.mapper.go new file mode 100644 index 0000000..7a94317 --- /dev/null +++ b/app/module/ebooks/mapper/ebooks.mapper.go @@ -0,0 +1,140 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + ebooksResponse "campaign-pool-be/app/module/ebooks/response" +) + +func ToEbooksResponse(ebook *entity.Ebooks) *ebooksResponse.EbooksResponse { + if ebook == nil { + return nil + } + + var authorName, authorEmail *string + if ebook.Author != nil { + authorName = &ebook.Author.Fullname + authorEmail = &ebook.Author.Email + } + + return &ebooksResponse.EbooksResponse{ + ID: ebook.ID, + Title: ebook.Title, + Slug: ebook.Slug, + Description: ebook.Description, + Price: ebook.Price, + PdfFilePath: ebook.PdfFilePath, + PdfFileName: ebook.PdfFileName, + PdfFileSize: ebook.PdfFileSize, + ThumbnailPath: ebook.ThumbnailPath, + ThumbnailName: ebook.ThumbnailName, + AuthorId: ebook.AuthorId, + AuthorName: authorName, + AuthorEmail: authorEmail, + Category: ebook.Category, + Tags: ebook.Tags, + PageCount: ebook.PageCount, + Language: ebook.Language, + Isbn: ebook.Isbn, + Publisher: ebook.Publisher, + PublishedYear: ebook.PublishedYear, + DownloadCount: ebook.DownloadCount, + PurchaseCount: ebook.PurchaseCount, + WishlistCount: ebook.WishlistCount, + Rating: ebook.Rating, + ReviewCount: ebook.ReviewCount, + StatusId: ebook.StatusId, + IsActive: ebook.IsActive, + IsPublished: ebook.IsPublished, + PublishedAt: ebook.PublishedAt, + CreatedById: ebook.CreatedById, + CreatedAt: ebook.CreatedAt, + UpdatedAt: ebook.UpdatedAt, + } +} + +func ToEbooksResponseList(ebooks []*entity.Ebooks) []*ebooksResponse.EbooksResponse { + if ebooks == nil { + return nil + } + + var responses []*ebooksResponse.EbooksResponse + for _, ebook := range ebooks { + responses = append(responses, ToEbooksResponse(ebook)) + } + + return responses +} + +func ToEbookWishlistResponse(wishlist *entity.EbookWishlists) *ebooksResponse.EbookWishlistResponse { + if wishlist == nil { + return nil + } + + return &ebooksResponse.EbookWishlistResponse{ + ID: wishlist.ID, + UserId: wishlist.UserId, + EbookId: wishlist.EbookId, + Ebook: ToEbooksResponse(wishlist.Ebook), + CreatedAt: wishlist.CreatedAt, + UpdatedAt: wishlist.UpdatedAt, + } +} + +func ToEbookWishlistResponseList(wishlists []*entity.EbookWishlists) []*ebooksResponse.EbookWishlistResponse { + if wishlists == nil { + return nil + } + + var responses []*ebooksResponse.EbookWishlistResponse + for _, wishlist := range wishlists { + responses = append(responses, ToEbookWishlistResponse(wishlist)) + } + + return responses +} + +func ToEbookPurchaseResponse(purchase *entity.EbookPurchases) *ebooksResponse.EbookPurchaseResponse { + if purchase == nil { + return nil + } + + var buyerName, buyerEmail *string + if purchase.Buyer != nil { + buyerName = &purchase.Buyer.Fullname + buyerEmail = &purchase.Buyer.Email + } + + return &ebooksResponse.EbookPurchaseResponse{ + ID: purchase.ID, + BuyerId: purchase.BuyerId, + BuyerName: buyerName, + BuyerEmail: buyerEmail, + EbookId: purchase.EbookId, + Ebook: ToEbooksResponse(purchase.Ebook), + PurchasePrice: purchase.PurchasePrice, + PaymentMethod: purchase.PaymentMethod, + PaymentStatus: purchase.PaymentStatus, + TransactionId: purchase.TransactionId, + PaymentProof: purchase.PaymentProof, + PaymentDate: purchase.PaymentDate, + DownloadCount: purchase.DownloadCount, + LastDownloadAt: purchase.LastDownloadAt, + StatusId: purchase.StatusId, + IsActive: purchase.IsActive, + CreatedAt: purchase.CreatedAt, + UpdatedAt: purchase.UpdatedAt, + } +} + +func ToEbookPurchaseResponseList(purchases []*entity.EbookPurchases) []*ebooksResponse.EbookPurchaseResponse { + if purchases == nil { + return nil + } + + var responses []*ebooksResponse.EbookPurchaseResponse + for _, purchase := range purchases { + responses = append(responses, ToEbookPurchaseResponse(purchase)) + } + + return responses +} diff --git a/app/module/ebooks/repository/ebook_purchases.repository.go b/app/module/ebooks/repository/ebook_purchases.repository.go new file mode 100644 index 0000000..5410861 --- /dev/null +++ b/app/module/ebooks/repository/ebook_purchases.repository.go @@ -0,0 +1,98 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type ebookPurchasesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// EbookPurchasesRepository define interface of IEbookPurchasesRepository +type EbookPurchasesRepository interface { + GetByBuyerId(buyerId uint, pagination *paginator.Pagination) (purchases []*entity.EbookPurchases, paging paginator.Pagination, err error) + FindByBuyerAndEbook(buyerId uint, ebookId uint) (purchase *entity.EbookPurchases, err error) + FindOne(id uint) (purchase *entity.EbookPurchases, err error) + Create(purchase *entity.EbookPurchases) (purchaseReturn *entity.EbookPurchases, err error) + Update(id uint, purchase *entity.EbookPurchases) (err error) + UpdateDownloadCount(id uint) (err error) + Delete(id uint) (err error) +} + +func NewEbookPurchasesRepository(db *database.Database, log zerolog.Logger) EbookPurchasesRepository { + return &ebookPurchasesRepository{ + DB: db, + Log: log, + } +} + +func (_i *ebookPurchasesRepository) GetByBuyerId(buyerId uint, pagination *paginator.Pagination) (purchases []*entity.EbookPurchases, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.EbookPurchases{}). + Preload("Ebook"). + Preload("Ebook.Author"). + Preload("Buyer"). + Where("buyer_id = ?", buyerId) + + query.Count(&count) + + pagination.Count = count + pagination = paginator.Paging(pagination) + + err = query.Offset(pagination.Offset).Limit(pagination.Limit). + Order("created_at DESC"). + Find(&purchases).Error + if err != nil { + return + } + + paging = *pagination + + return +} + +func (_i *ebookPurchasesRepository) FindByBuyerAndEbook(buyerId uint, ebookId uint) (purchase *entity.EbookPurchases, err error) { + query := _i.DB.DB.Where("buyer_id = ? AND ebook_id = ?", buyerId, ebookId) + + if err := query.First(&purchase).Error; err != nil { + return nil, err + } + + return purchase, nil +} + +func (_i *ebookPurchasesRepository) FindOne(id uint) (purchase *entity.EbookPurchases, err error) { + query := _i.DB.DB.Preload("Ebook").Preload("Buyer") + if err := query.First(&purchase, id).Error; err != nil { + return nil, err + } + + return purchase, nil +} + +func (_i *ebookPurchasesRepository) Create(purchase *entity.EbookPurchases) (purchaseReturn *entity.EbookPurchases, err error) { + result := _i.DB.DB.Create(purchase) + return purchase, result.Error +} + +func (_i *ebookPurchasesRepository) Update(id uint, purchase *entity.EbookPurchases) (err error) { + return _i.DB.DB.Model(&entity.EbookPurchases{}). + Where(&entity.EbookPurchases{ID: id}). + Updates(purchase).Error +} + +func (_i *ebookPurchasesRepository) UpdateDownloadCount(id uint) (err error) { + return _i.DB.DB.Model(&entity.EbookPurchases{}). + Where("id = ?", id). + Update("download_count", "download_count + 1").Error +} + +func (_i *ebookPurchasesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.EbookPurchases{}, id).Error +} diff --git a/app/module/ebooks/repository/ebook_ratings.repository.go b/app/module/ebooks/repository/ebook_ratings.repository.go new file mode 100644 index 0000000..fdb3aa4 --- /dev/null +++ b/app/module/ebooks/repository/ebook_ratings.repository.go @@ -0,0 +1,243 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ebooks/request" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type ebookRatingsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// EbookRatingsRepository define interface of IEbookRatingsRepository +type EbookRatingsRepository interface { + GetAll(req request.EbookRatingsQueryRequest) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) + GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) + GetByUserId(userId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) + FindOne(id uint) (rating *entity.EbookRatings, err error) + FindByUserAndEbook(userId uint, ebookId uint) (rating *entity.EbookRatings, err error) + FindByPurchaseId(purchaseId uint) (rating *entity.EbookRatings, err error) + Create(rating *entity.EbookRatings) (ratingReturn *entity.EbookRatings, err error) + Update(id uint, rating *entity.EbookRatings) (err error) + Delete(id uint) (err error) + GetEbookRatingStats(ebookId uint) (totalRatings int, averageRating float64, ratingCounts map[int]int, err error) + GetRecentReviews(ebookId uint, limit int) (reviews []*entity.EbookRatings, err error) +} + +func NewEbookRatingsRepository(db *database.Database, log zerolog.Logger) EbookRatingsRepository { + return &ebookRatingsRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IEbookRatingsRepository +func (_i *ebookRatingsRepository) GetAll(req request.EbookRatingsQueryRequest) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.EbookRatings{}). + Preload("User"). + Preload("Ebook"). + Preload("Purchase"). + Where("ebook_ratings.is_active = ?", true) + + if req.EbookId != nil { + query = query.Where("ebook_ratings.ebook_id = ?", req.EbookId) + } + if req.UserId != nil { + query = query.Where("ebook_ratings.user_id = ?", req.UserId) + } + if req.Rating != nil { + query = query.Where("ebook_ratings.rating = ?", req.Rating) + } + if req.IsVerified != nil { + query = query.Where("ebook_ratings.is_verified = ?", req.IsVerified) + } + if req.StatusId != nil { + query = query.Where("ebook_ratings.status_id = ?", req.StatusId) + } + + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order("ebook_ratings." + req.Pagination.SortBy + " " + direction) + } else { + query.Order("ebook_ratings.created_at DESC") + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&ratings).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *ebookRatingsRepository) GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.EbookRatings{}). + Preload("User"). + Preload("Ebook"). + Preload("Purchase"). + Where("ebook_ratings.ebook_id = ? AND ebook_ratings.is_active = ?", ebookId, true) + + query.Count(&count) + + pagination.Count = count + pagination = paginator.Paging(pagination) + + err = query.Offset(pagination.Offset).Limit(pagination.Limit). + Order("ebook_ratings.created_at DESC"). + Find(&ratings).Error + if err != nil { + return + } + + paging = *pagination + + return +} + +func (_i *ebookRatingsRepository) GetByUserId(userId uint, pagination *paginator.Pagination) (ratings []*entity.EbookRatings, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.EbookRatings{}). + Preload("User"). + Preload("Ebook"). + Preload("Purchase"). + Where("ebook_ratings.user_id = ? AND ebook_ratings.is_active = ?", userId, true) + + query.Count(&count) + + pagination.Count = count + pagination = paginator.Paging(pagination) + + err = query.Offset(pagination.Offset).Limit(pagination.Limit). + Order("ebook_ratings.created_at DESC"). + Find(&ratings).Error + if err != nil { + return + } + + paging = *pagination + + return +} + +func (_i *ebookRatingsRepository) FindOne(id uint) (rating *entity.EbookRatings, err error) { + query := _i.DB.DB.Preload("User").Preload("Ebook").Preload("Purchase") + if err := query.First(&rating, id).Error; err != nil { + return nil, err + } + + return rating, nil +} + +func (_i *ebookRatingsRepository) FindByUserAndEbook(userId uint, ebookId uint) (rating *entity.EbookRatings, err error) { + query := _i.DB.DB.Where("user_id = ? AND ebook_id = ?", userId, ebookId) + + if err := query.First(&rating).Error; err != nil { + return nil, err + } + + return rating, nil +} + +func (_i *ebookRatingsRepository) FindByPurchaseId(purchaseId uint) (rating *entity.EbookRatings, err error) { + query := _i.DB.DB.Where("purchase_id = ?", purchaseId) + + if err := query.First(&rating).Error; err != nil { + return nil, err + } + + return rating, nil +} + +func (_i *ebookRatingsRepository) Create(rating *entity.EbookRatings) (ratingReturn *entity.EbookRatings, err error) { + result := _i.DB.DB.Create(rating) + return rating, result.Error +} + +func (_i *ebookRatingsRepository) Update(id uint, rating *entity.EbookRatings) (err error) { + return _i.DB.DB.Model(&entity.EbookRatings{}). + Where(&entity.EbookRatings{ID: id}). + Updates(rating).Error +} + +func (_i *ebookRatingsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.EbookRatings{}, id).Error +} + +func (_i *ebookRatingsRepository) GetEbookRatingStats(ebookId uint) (totalRatings int, averageRating float64, ratingCounts map[int]int, err error) { + var stats struct { + TotalRatings int `json:"totalRatings"` + AverageRating float64 `json:"averageRating"` + } + + // Get total ratings and average + err = _i.DB.DB.Model(&entity.EbookRatings{}). + Select("COUNT(*) as total_ratings, COALESCE(AVG(rating), 0) as average_rating"). + Where("ebook_id = ? AND is_active = ? AND is_verified = ?", ebookId, true, true). + Scan(&stats).Error + if err != nil { + return 0, 0, nil, err + } + + // Get rating counts + var ratingCountsData []struct { + Rating int `json:"rating"` + Count int `json:"count"` + } + + err = _i.DB.DB.Model(&entity.EbookRatings{}). + Select("rating, COUNT(*) as count"). + Where("ebook_id = ? AND is_active = ? AND is_verified = ?", ebookId, true, true). + Group("rating"). + Scan(&ratingCountsData).Error + if err != nil { + return 0, 0, nil, err + } + + // Convert to map + ratingCounts = make(map[int]int) + for _, rc := range ratingCountsData { + ratingCounts[rc.Rating] = rc.Count + } + + // Ensure all ratings 1-5 are present + for i := 1; i <= 5; i++ { + if _, exists := ratingCounts[i]; !exists { + ratingCounts[i] = 0 + } + } + + return stats.TotalRatings, stats.AverageRating, ratingCounts, nil +} + +func (_i *ebookRatingsRepository) GetRecentReviews(ebookId uint, limit int) (reviews []*entity.EbookRatings, err error) { + query := _i.DB.DB.Model(&entity.EbookRatings{}). + Preload("User"). + Preload("Ebook"). + Preload("Purchase"). + Where("ebook_id = ? AND is_active = ? AND is_verified = ? AND review IS NOT NULL AND review != ''", ebookId, true, true). + Order("created_at DESC"). + Limit(limit) + + err = query.Find(&reviews).Error + return reviews, err +} diff --git a/app/module/ebooks/repository/ebook_wishlists.repository.go b/app/module/ebooks/repository/ebook_wishlists.repository.go new file mode 100644 index 0000000..801004a --- /dev/null +++ b/app/module/ebooks/repository/ebook_wishlists.repository.go @@ -0,0 +1,79 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type ebookWishlistsRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// EbookWishlistsRepository define interface of IEbookWishlistsRepository +type EbookWishlistsRepository interface { + GetByUserId(userId uint, pagination *paginator.Pagination) (wishlists []*entity.EbookWishlists, paging paginator.Pagination, err error) + FindByUserAndEbook(userId uint, ebookId uint) (wishlist *entity.EbookWishlists, err error) + Create(wishlist *entity.EbookWishlists) (wishlistReturn *entity.EbookWishlists, err error) + Delete(id uint) (err error) + DeleteByUserAndEbook(userId uint, ebookId uint) (err error) +} + +func NewEbookWishlistsRepository(db *database.Database, log zerolog.Logger) EbookWishlistsRepository { + return &ebookWishlistsRepository{ + DB: db, + Log: log, + } +} + +func (_i *ebookWishlistsRepository) GetByUserId(userId uint, pagination *paginator.Pagination) (wishlists []*entity.EbookWishlists, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.EbookWishlists{}). + Preload("Ebook"). + Preload("Ebook.Author"). + Where("user_id = ?", userId) + + query.Count(&count) + + pagination.Count = count + pagination = paginator.Paging(pagination) + + err = query.Offset(pagination.Offset).Limit(pagination.Limit). + Order("created_at DESC"). + Find(&wishlists).Error + if err != nil { + return + } + + paging = *pagination + + return +} + +func (_i *ebookWishlistsRepository) FindByUserAndEbook(userId uint, ebookId uint) (wishlist *entity.EbookWishlists, err error) { + query := _i.DB.DB.Where("user_id = ? AND ebook_id = ?", userId, ebookId) + + if err := query.First(&wishlist).Error; err != nil { + return nil, err + } + + return wishlist, nil +} + +func (_i *ebookWishlistsRepository) Create(wishlist *entity.EbookWishlists) (wishlistReturn *entity.EbookWishlists, err error) { + result := _i.DB.DB.Create(wishlist) + return wishlist, result.Error +} + +func (_i *ebookWishlistsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.EbookWishlists{}, id).Error +} + +func (_i *ebookWishlistsRepository) DeleteByUserAndEbook(userId uint, ebookId uint) error { + return _i.DB.DB.Where("user_id = ? AND ebook_id = ?", userId, ebookId). + Delete(&entity.EbookWishlists{}).Error +} diff --git a/app/module/ebooks/repository/ebooks.repository.go b/app/module/ebooks/repository/ebooks.repository.go new file mode 100644 index 0000000..e1b92bc --- /dev/null +++ b/app/module/ebooks/repository/ebooks.repository.go @@ -0,0 +1,186 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ebooks/request" + "campaign-pool-be/app/module/ebooks/response" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type ebooksRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// EbooksRepository define interface of IEbooksRepository +type EbooksRepository interface { + GetAll(req request.EbooksQueryRequest) (ebooks []*entity.Ebooks, paging paginator.Pagination, err error) + FindOne(id uint) (ebook *entity.Ebooks, err error) + FindBySlug(slug string) (ebook *entity.Ebooks, err error) + Create(ebook *entity.Ebooks) (ebookReturn *entity.Ebooks, err error) + Update(id uint, ebook *entity.Ebooks) (err error) + UpdateSkipNull(id uint, ebook *entity.Ebooks) (err error) + Delete(id uint) (err error) + UpdateDownloadCount(id uint) (err error) + UpdatePurchaseCount(id uint) (err error) + UpdateWishlistCount(id uint) (err error) + SummaryStats(authorId uint) (ebookSummaryStats *response.EbookSummaryStats, err error) +} + +func NewEbooksRepository(db *database.Database, log zerolog.Logger) EbooksRepository { + return &ebooksRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IEbooksRepository +func (_i *ebooksRepository) GetAll(req request.EbooksQueryRequest) (ebooks []*entity.Ebooks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Ebooks{}). + Preload("Author"). + Where("ebooks.is_active = ?", true) + + if req.Title != nil && *req.Title != "" { + title := strings.ToLower(*req.Title) + query = query.Where("LOWER(ebooks.title) LIKE ?", "%"+strings.ToLower(title)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(ebooks.description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.AuthorId != nil { + query = query.Where("ebooks.author_id = ?", req.AuthorId) + } + if req.Category != nil && *req.Category != "" { + category := strings.ToLower(*req.Category) + query = query.Where("LOWER(ebooks.category) LIKE ?", "%"+strings.ToLower(category)+"%") + } + if req.Tags != nil && *req.Tags != "" { + tags := strings.ToLower(*req.Tags) + query = query.Where("LOWER(ebooks.tags) LIKE ?", "%"+strings.ToLower(tags)+"%") + } + if req.MinPrice != nil { + query = query.Where("ebooks.price >= ?", req.MinPrice) + } + if req.MaxPrice != nil { + query = query.Where("ebooks.price <= ?", req.MaxPrice) + } + if req.IsPublished != nil { + query = query.Where("ebooks.is_published = ?", req.IsPublished) + } + if req.StatusId != nil { + query = query.Where("ebooks.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)) + } else { + direction := "DESC" + sortBy := "ebooks.created_at" + query.Order(fmt.Sprintf("%s %s", sortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&ebooks).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *ebooksRepository) FindOne(id uint) (ebook *entity.Ebooks, err error) { + query := _i.DB.DB.Preload("Author") + if err := query.First(&ebook, id).Error; err != nil { + return nil, err + } + + return ebook, nil +} + +func (_i *ebooksRepository) FindBySlug(slug string) (ebook *entity.Ebooks, err error) { + query := _i.DB.DB.Preload("Author").Where("slug = ?", slug) + + if err := query.First(&ebook).Error; err != nil { + return nil, err + } + + return ebook, nil +} + +func (_i *ebooksRepository) Create(ebook *entity.Ebooks) (ebookReturn *entity.Ebooks, err error) { + result := _i.DB.DB.Create(ebook) + return ebook, result.Error +} + +func (_i *ebooksRepository) Update(id uint, ebook *entity.Ebooks) (err error) { + ebookMap, err := utilSvc.StructToMap(ebook) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.Ebooks{}). + Where(&entity.Ebooks{ID: id}). + Updates(ebookMap).Error +} + +func (_i *ebooksRepository) UpdateSkipNull(id uint, ebook *entity.Ebooks) (err error) { + return _i.DB.DB.Model(&entity.Ebooks{}). + Where(&entity.Ebooks{ID: id}). + Updates(ebook).Error +} + +func (_i *ebooksRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Ebooks{}, id).Error +} + +func (_i *ebooksRepository) UpdateDownloadCount(id uint) (err error) { + return _i.DB.DB.Model(&entity.Ebooks{}). + Where("id = ?", id). + Update("download_count", "download_count + 1").Error +} + +func (_i *ebooksRepository) UpdatePurchaseCount(id uint) (err error) { + return _i.DB.DB.Model(&entity.Ebooks{}). + Where("id = ?", id). + Update("purchase_count", "purchase_count + 1").Error +} + +func (_i *ebooksRepository) UpdateWishlistCount(id uint) (err error) { + return _i.DB.DB.Model(&entity.Ebooks{}). + Where("id = ?", id). + Update("wishlist_count", "wishlist_count + 1").Error +} + +func (_i *ebooksRepository) SummaryStats(authorId uint) (ebookSummaryStats *response.EbookSummaryStats, err error) { + query := _i.DB.DB.Model(&entity.Ebooks{}). + Select( + "COUNT(*) AS total_ebooks, "+ + "COALESCE(SUM(purchase_count), 0) AS total_sales, "+ + "COALESCE(SUM(price * purchase_count), 0) AS total_revenue, "+ + "COALESCE(SUM(download_count), 0) AS total_downloads, "+ + "COALESCE(SUM(wishlist_count), 0) AS total_wishlists, "+ + "COALESCE(AVG(rating), 0) AS average_rating"). + Where("author_id = ?", authorId) + + err = query.Scan(&ebookSummaryStats).Error + + return ebookSummaryStats, err +} diff --git a/app/module/ebooks/request/ebook_ratings.request.go b/app/module/ebooks/request/ebook_ratings.request.go new file mode 100644 index 0000000..8da2a56 --- /dev/null +++ b/app/module/ebooks/request/ebook_ratings.request.go @@ -0,0 +1,113 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type EbookRatingsGeneric interface { + ToEntity() +} + +type EbookRatingsQueryRequest struct { + EbookId *uint `json:"ebookId"` + UserId *uint `json:"userId"` + Rating *int `json:"rating"` + IsVerified *bool `json:"isVerified"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type EbookRatingsCreateRequest struct { + EbookId uint `json:"ebookId" validate:"required"` + PurchaseId uint `json:"purchaseId" validate:"required"` + Rating int `json:"rating" validate:"required,min=1,max=5"` + Review *string `json:"review"` + IsAnonymous *bool `json:"isAnonymous"` +} + +func (req EbookRatingsCreateRequest) ToEntity() *entity.EbookRatings { + return &entity.EbookRatings{ + EbookId: req.EbookId, + PurchaseId: req.PurchaseId, + Rating: req.Rating, + Review: req.Review, + IsAnonymous: req.IsAnonymous, + IsVerified: boolPtr(true), + StatusId: intPtr(1), + IsActive: boolPtr(true), + } +} + +type EbookRatingsUpdateRequest struct { + Rating int `json:"rating" validate:"required,min=1,max=5"` + Review *string `json:"review"` + IsAnonymous *bool `json:"isAnonymous"` +} + +func (req EbookRatingsUpdateRequest) ToEntity() *entity.EbookRatings { + return &entity.EbookRatings{ + Rating: req.Rating, + Review: req.Review, + IsAnonymous: req.IsAnonymous, + UpdatedAt: time.Now(), + } +} + +type EbookRatingsQueryRequestContext struct { + EbookId string `json:"ebookId"` + UserId string `json:"userId"` + Rating string `json:"rating"` + IsVerified string `json:"isVerified"` + StatusId string `json:"statusId"` +} + +func (req EbookRatingsQueryRequestContext) ToParamRequest() EbookRatingsQueryRequest { + var request EbookRatingsQueryRequest + + if ebookIdStr := req.EbookId; ebookIdStr != "" { + ebookId, err := strconv.Atoi(ebookIdStr) + if err == nil { + ebookIdUint := uint(ebookId) + request.EbookId = &ebookIdUint + } + } + if userIdStr := req.UserId; userIdStr != "" { + userId, err := strconv.Atoi(userIdStr) + if err == nil { + userIdUint := uint(userId) + request.UserId = &userIdUint + } + } + if ratingStr := req.Rating; ratingStr != "" { + rating, err := strconv.Atoi(ratingStr) + if err == nil { + request.Rating = &rating + } + } + if isVerifiedStr := req.IsVerified; isVerifiedStr != "" { + isVerified, err := strconv.ParseBool(isVerifiedStr) + if err == nil { + request.IsVerified = &isVerified + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} + +// Helper functions +func boolPtr(b bool) *bool { + return &b +} + +func intPtr(i int) *int { + return &i +} diff --git a/app/module/ebooks/request/ebooks.request.go b/app/module/ebooks/request/ebooks.request.go new file mode 100644 index 0000000..5b871e1 --- /dev/null +++ b/app/module/ebooks/request/ebooks.request.go @@ -0,0 +1,178 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type EbooksGeneric interface { + ToEntity() +} + +type EbooksQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + AuthorId *uint `json:"authorId"` + Category *string `json:"category"` + Tags *string `json:"tags"` + MinPrice *float64 `json:"minPrice"` + MaxPrice *float64 `json:"maxPrice"` + StatusId *int `json:"statusId"` + IsPublished *bool `json:"isPublished"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type EbooksCreateRequest struct { + Title string `json:"title" validate:"required"` + Slug string `json:"slug" validate:"required"` + Description string `json:"description" validate:"required"` + Price float64 `json:"price" validate:"required,min=0"` + Category *string `json:"category"` + Tags *string `json:"tags"` + PageCount *int `json:"pageCount"` + Language *string `json:"language"` + Isbn *string `json:"isbn"` + Publisher *string `json:"publisher"` + PublishedYear *int `json:"publishedYear"` + IsPublished *bool `json:"isPublished"` + CreatedAt *string `json:"createdAt"` + CreatedById *uint `json:"createdById"` +} + +func (req EbooksCreateRequest) ToEntity() *entity.Ebooks { + return &entity.Ebooks{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + Price: req.Price, + Category: req.Category, + Tags: req.Tags, + PageCount: req.PageCount, + Language: req.Language, + Isbn: req.Isbn, + Publisher: req.Publisher, + PublishedYear: req.PublishedYear, + IsPublished: req.IsPublished, + } +} + +type EbooksUpdateRequest struct { + Title string `json:"title" validate:"required"` + Slug string `json:"slug" validate:"required"` + Description string `json:"description" validate:"required"` + Price float64 `json:"price" validate:"required,min=0"` + Category *string `json:"category"` + Tags *string `json:"tags"` + PageCount *int `json:"pageCount"` + Language *string `json:"language"` + Isbn *string `json:"isbn"` + Publisher *string `json:"publisher"` + PublishedYear *int `json:"publishedYear"` + IsPublished *bool `json:"isPublished"` + StatusId *int `json:"statusId"` + CreatedAt *string `json:"createdAt"` + CreatedById *uint `json:"createdById"` +} + +func (req EbooksUpdateRequest) ToEntity() *entity.Ebooks { + if req.CreatedById == nil { + return &entity.Ebooks{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + Price: req.Price, + Category: req.Category, + Tags: req.Tags, + PageCount: req.PageCount, + Language: req.Language, + Isbn: req.Isbn, + Publisher: req.Publisher, + PublishedYear: req.PublishedYear, + IsPublished: req.IsPublished, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } + } else { + return &entity.Ebooks{ + Title: req.Title, + Slug: req.Slug, + Description: req.Description, + Price: req.Price, + Category: req.Category, + Tags: req.Tags, + PageCount: req.PageCount, + Language: req.Language, + Isbn: req.Isbn, + Publisher: req.Publisher, + PublishedYear: req.PublishedYear, + IsPublished: req.IsPublished, + StatusId: req.StatusId, + CreatedById: req.CreatedById, + UpdatedAt: time.Now(), + } + } +} + +type EbooksQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + AuthorId string `json:"authorId"` + Category string `json:"category"` + Tags string `json:"tags"` + MinPrice string `json:"minPrice"` + MaxPrice string `json:"maxPrice"` + StatusId string `json:"statusId"` + IsPublished string `json:"isPublished"` +} + +func (req EbooksQueryRequestContext) ToParamRequest() EbooksQueryRequest { + var request EbooksQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if category := req.Category; category != "" { + request.Category = &category + } + if tags := req.Tags; tags != "" { + request.Tags = &tags + } + if authorIdStr := req.AuthorId; authorIdStr != "" { + authorId, err := strconv.Atoi(authorIdStr) + if err == nil { + authorIdUint := uint(authorId) + request.AuthorId = &authorIdUint + } + } + if minPriceStr := req.MinPrice; minPriceStr != "" { + minPrice, err := strconv.ParseFloat(minPriceStr, 64) + if err == nil { + request.MinPrice = &minPrice + } + } + if maxPriceStr := req.MaxPrice; maxPriceStr != "" { + maxPrice, err := strconv.ParseFloat(maxPriceStr, 64) + if err == nil { + request.MaxPrice = &maxPrice + } + } + if isPublishedStr := req.IsPublished; isPublishedStr != "" { + isPublished, err := strconv.ParseBool(isPublishedStr) + if err == nil { + request.IsPublished = &isPublished + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/ebooks/response/ebook_ratings.response.go b/app/module/ebooks/response/ebook_ratings.response.go new file mode 100644 index 0000000..124f466 --- /dev/null +++ b/app/module/ebooks/response/ebook_ratings.response.go @@ -0,0 +1,43 @@ +package response + +import ( + "time" +) + +type EbookRatingsResponse struct { + ID uint `json:"id"` + UserId uint `json:"userId"` + UserName *string `json:"userName"` + UserEmail *string `json:"userEmail"` + EbookId uint `json:"ebookId"` + EbookTitle *string `json:"ebookTitle"` + PurchaseId uint `json:"purchaseId"` + Rating int `json:"rating"` + Review *string `json:"review"` + IsAnonymous *bool `json:"isAnonymous"` + IsVerified *bool `json:"isVerified"` + StatusId *int `json:"statusId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type EbookRatingSummaryResponse struct { + EbookId uint `json:"ebookId"` + EbookTitle string `json:"ebookTitle"` + AverageRating float64 `json:"averageRating"` + TotalRatings int `json:"totalRatings"` + RatingCounts map[int]int `json:"ratingCounts"` + RecentReviews []EbookRatingsResponse `json:"recentReviews"` +} + +type EbookRatingStatsResponse struct { + TotalRatings int `json:"totalRatings"` + AverageRating float64 `json:"averageRating"` + RatingCounts map[int]int `json:"ratingCounts"` + FiveStarCount int `json:"fiveStarCount"` + FourStarCount int `json:"fourStarCount"` + ThreeStarCount int `json:"threeStarCount"` + TwoStarCount int `json:"twoStarCount"` + OneStarCount int `json:"oneStarCount"` +} diff --git a/app/module/ebooks/response/ebooks.response.go b/app/module/ebooks/response/ebooks.response.go new file mode 100644 index 0000000..714e258 --- /dev/null +++ b/app/module/ebooks/response/ebooks.response.go @@ -0,0 +1,79 @@ +package response + +import ( + "time" +) + +type EbooksResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` + Price float64 `json:"price"` + PdfFilePath *string `json:"pdfFilePath"` + PdfFileName *string `json:"pdfFileName"` + PdfFileSize *int64 `json:"pdfFileSize"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailName *string `json:"thumbnailName"` + AuthorId uint `json:"authorId"` + AuthorName *string `json:"authorName"` + AuthorEmail *string `json:"authorEmail"` + Category *string `json:"category"` + Tags *string `json:"tags"` + PageCount *int `json:"pageCount"` + Language *string `json:"language"` + Isbn *string `json:"isbn"` + Publisher *string `json:"publisher"` + PublishedYear *int `json:"publishedYear"` + DownloadCount *int `json:"downloadCount"` + PurchaseCount *int `json:"purchaseCount"` + WishlistCount *int `json:"wishlistCount"` + Rating *float64 `json:"rating"` + ReviewCount *int `json:"reviewCount"` + StatusId *int `json:"statusId"` + IsActive *bool `json:"isActive"` + IsPublished *bool `json:"isPublished"` + PublishedAt *time.Time `json:"publishedAt"` + CreatedById *uint `json:"createdById"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type EbookWishlistResponse struct { + ID uint `json:"id"` + UserId uint `json:"userId"` + EbookId uint `json:"ebookId"` + Ebook *EbooksResponse `json:"ebook"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type EbookPurchaseResponse struct { + ID uint `json:"id"` + BuyerId uint `json:"buyerId"` + BuyerName *string `json:"buyerName"` + BuyerEmail *string `json:"buyerEmail"` + EbookId uint `json:"ebookId"` + Ebook *EbooksResponse `json:"ebook"` + PurchasePrice float64 `json:"purchasePrice"` + PaymentMethod *string `json:"paymentMethod"` + PaymentStatus *string `json:"paymentStatus"` + TransactionId *string `json:"transactionId"` + PaymentProof *string `json:"paymentProof"` + PaymentDate *time.Time `json:"paymentDate"` + DownloadCount *int `json:"downloadCount"` + LastDownloadAt *time.Time `json:"lastDownloadAt"` + StatusId *int `json:"statusId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type EbookSummaryStats struct { + TotalEbooks int `json:"totalEbooks"` + TotalSales int `json:"totalSales"` + TotalRevenue float64 `json:"totalRevenue"` + TotalDownloads int `json:"totalDownloads"` + TotalWishlists int `json:"totalWishlists"` + AverageRating float64 `json:"averageRating"` +} diff --git a/app/module/ebooks/service/ebook_purchases.service.go b/app/module/ebooks/service/ebook_purchases.service.go new file mode 100644 index 0000000..007b791 --- /dev/null +++ b/app/module/ebooks/service/ebook_purchases.service.go @@ -0,0 +1,185 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ebooks/mapper" + "campaign-pool-be/app/module/ebooks/repository" + "campaign-pool-be/app/module/ebooks/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + "time" + + "github.com/rs/zerolog" +) + +// EbookPurchasesService +type ebookPurchasesService struct { + Repo repository.EbookPurchasesRepository + EbooksRepo repository.EbooksRepository + Log zerolog.Logger + UsersRepo usersRepository.UsersRepository +} + +// EbookPurchasesService define interface of IEbookPurchasesService +type EbookPurchasesService interface { + GetByBuyerId(authToken string, pagination *paginator.Pagination) (purchases []*response.EbookPurchaseResponse, paging paginator.Pagination, err error) + PurchaseEbook(authToken string, ebookId uint, paymentMethod string) (purchase *response.EbookPurchaseResponse, err error) + UpdatePaymentStatus(purchaseId uint, paymentStatus string, transactionId string, paymentProof string) (err error) + GetPurchaseById(purchaseId uint) (purchase *response.EbookPurchaseResponse, err error) + ConfirmPayment(purchaseId uint) (err error) +} + +// NewEbookPurchasesService init EbookPurchasesService +func NewEbookPurchasesService( + repo repository.EbookPurchasesRepository, + ebooksRepo repository.EbooksRepository, + log zerolog.Logger, + usersRepo usersRepository.UsersRepository) EbookPurchasesService { + + return &ebookPurchasesService{ + Repo: repo, + EbooksRepo: ebooksRepo, + Log: log, + UsersRepo: usersRepo, + } +} + +// GetByBuyerId implement interface of EbookPurchasesService +func (_i *ebookPurchasesService) GetByBuyerId(authToken string, pagination *paginator.Pagination) (purchases []*response.EbookPurchaseResponse, paging paginator.Pagination, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, paginator.Pagination{}, errors.New("user not found") + } + + purchasesData, paging, err := _i.Repo.GetByBuyerId(user.ID, pagination) + if err != nil { + return nil, paging, err + } + + purchases = mapper.ToEbookPurchaseResponseList(purchasesData) + + return purchases, paging, nil +} + +// PurchaseEbook implement interface of EbookPurchasesService +func (_i *ebookPurchasesService) PurchaseEbook(authToken string, ebookId uint, paymentMethod string) (purchase *response.EbookPurchaseResponse, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, errors.New("user not found") + } + + // Check if ebook exists + ebook, err := _i.EbooksRepo.FindOne(ebookId) + if err != nil { + return nil, errors.New("ebook not found") + } + + // Check if already purchased + existingPurchase, err := _i.Repo.FindByBuyerAndEbook(user.ID, ebookId) + if err == nil && existingPurchase != nil { + return nil, errors.New("ebook already purchased") + } + + // Create purchase record + purchaseEntity := &entity.EbookPurchases{ + BuyerId: user.ID, + EbookId: ebookId, + PurchasePrice: ebook.Price, + PaymentMethod: &paymentMethod, + PaymentStatus: stringPtr("pending"), + StatusId: intPtr(1), + IsActive: boolPtr(true), + } + + purchaseData, err := _i.Repo.Create(purchaseEntity) + if err != nil { + return nil, err + } + + // Update purchase count + err = _i.EbooksRepo.UpdatePurchaseCount(ebookId) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update purchase count") + } + + purchase = mapper.ToEbookPurchaseResponse(purchaseData) + + return purchase, nil +} + +// UpdatePaymentStatus implement interface of EbookPurchasesService +func (_i *ebookPurchasesService) UpdatePaymentStatus(purchaseId uint, paymentStatus string, transactionId string, paymentProof string) (err error) { + _, err = _i.Repo.FindOne(purchaseId) + if err != nil { + return errors.New("purchase not found") + } + + updateData := &entity.EbookPurchases{ + PaymentStatus: &paymentStatus, + TransactionId: &transactionId, + PaymentProof: &paymentProof, + } + + if paymentStatus == "paid" { + now := time.Now() + updateData.PaymentDate = &now + } + + err = _i.Repo.Update(purchaseId, updateData) + if err != nil { + return err + } + + return nil +} + +// GetPurchaseById implement interface of EbookPurchasesService +func (_i *ebookPurchasesService) GetPurchaseById(purchaseId uint) (purchase *response.EbookPurchaseResponse, err error) { + purchaseData, err := _i.Repo.FindOne(purchaseId) + if err != nil { + return nil, err + } + + purchase = mapper.ToEbookPurchaseResponse(purchaseData) + + return purchase, nil +} + +// ConfirmPayment implement interface of EbookPurchasesService +func (_i *ebookPurchasesService) ConfirmPayment(purchaseId uint) (err error) { + purchase, err := _i.Repo.FindOne(purchaseId) + if err != nil { + return errors.New("purchase not found") + } + + if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" { + return errors.New("payment not completed") + } + + // Update status to confirmed + updateData := &entity.EbookPurchases{ + StatusId: intPtr(2), // Assuming 2 is confirmed status + } + + err = _i.Repo.Update(purchaseId, updateData) + if err != nil { + return err + } + + return nil +} + +// Helper functions +func stringPtr(s string) *string { + return &s +} + +func intPtr(i int) *int { + return &i +} + +func boolPtr(b bool) *bool { + return &b +} diff --git a/app/module/ebooks/service/ebook_ratings.service.go b/app/module/ebooks/service/ebook_ratings.service.go new file mode 100644 index 0000000..a25ad79 --- /dev/null +++ b/app/module/ebooks/service/ebook_ratings.service.go @@ -0,0 +1,280 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ebooks/mapper" + "campaign-pool-be/app/module/ebooks/repository" + "campaign-pool-be/app/module/ebooks/request" + "campaign-pool-be/app/module/ebooks/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + + "github.com/rs/zerolog" +) + +// EbookRatingsService +type ebookRatingsService struct { + Repo repository.EbookRatingsRepository + EbooksRepo repository.EbooksRepository + EbookPurchasesRepo repository.EbookPurchasesRepository + Log zerolog.Logger + UsersRepo usersRepository.UsersRepository +} + +// EbookRatingsService define interface of IEbookRatingsService +type EbookRatingsService interface { + GetAll(req request.EbookRatingsQueryRequest) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) + GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) + GetByUserId(authToken string, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) + GetEbookRatingSummary(ebookId uint) (summary *response.EbookRatingSummaryResponse, err error) + Create(req request.EbookRatingsCreateRequest, authToken string) (rating *response.EbookRatingsResponse, err error) + Update(id uint, req request.EbookRatingsUpdateRequest, authToken string) (err error) + Delete(id uint, authToken string) error + GetRatingStats(ebookId uint) (stats *response.EbookRatingStatsResponse, err error) +} + +// NewEbookRatingsService init EbookRatingsService +func NewEbookRatingsService( + repo repository.EbookRatingsRepository, + ebooksRepo repository.EbooksRepository, + ebookPurchasesRepo repository.EbookPurchasesRepository, + log zerolog.Logger, + usersRepo usersRepository.UsersRepository) EbookRatingsService { + + return &ebookRatingsService{ + Repo: repo, + EbooksRepo: ebooksRepo, + EbookPurchasesRepo: ebookPurchasesRepo, + Log: log, + UsersRepo: usersRepo, + } +} + +// GetAll implement interface of EbookRatingsService +func (_i *ebookRatingsService) GetAll(req request.EbookRatingsQueryRequest) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) { + ratingsData, paging, err := _i.Repo.GetAll(req) + if err != nil { + return nil, paging, err + } + + ratings = mapper.ToEbookRatingsResponseList(ratingsData) + + return ratings, paging, nil +} + +// GetByEbookId implement interface of EbookRatingsService +func (_i *ebookRatingsService) GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) { + ratingsData, paging, err := _i.Repo.GetByEbookId(ebookId, pagination) + if err != nil { + return nil, paging, err + } + + ratings = mapper.ToEbookRatingsResponseList(ratingsData) + + return ratings, paging, nil +} + +// GetByUserId implement interface of EbookRatingsService +func (_i *ebookRatingsService) GetByUserId(authToken string, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, paginator.Pagination{}, errors.New("user not found") + } + + ratingsData, paging, err := _i.Repo.GetByUserId(user.ID, pagination) + if err != nil { + return nil, paging, err + } + + ratings = mapper.ToEbookRatingsResponseList(ratingsData) + + return ratings, paging, nil +} + +// GetEbookRatingSummary implement interface of EbookRatingsService +func (_i *ebookRatingsService) GetEbookRatingSummary(ebookId uint) (summary *response.EbookRatingSummaryResponse, err error) { + // Get ebook info + ebook, err := _i.EbooksRepo.FindOne(ebookId) + if err != nil { + return nil, errors.New("ebook not found") + } + + // Get rating stats + totalRatings, averageRating, ratingCounts, err := _i.Repo.GetEbookRatingStats(ebookId) + if err != nil { + return nil, err + } + + // Get recent reviews + recentReviews, err := _i.Repo.GetRecentReviews(ebookId, 5) + if err != nil { + return nil, err + } + + summary = mapper.ToEbookRatingSummaryResponse(ebookId, ebook.Title, averageRating, totalRatings, ratingCounts, recentReviews) + + return summary, nil +} + +// Create implement interface of EbookRatingsService +func (_i *ebookRatingsService) Create(req request.EbookRatingsCreateRequest, authToken string) (rating *response.EbookRatingsResponse, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, errors.New("user not found") + } + + // Check if ebook exists + _, err = _i.EbooksRepo.FindOne(req.EbookId) + if err != nil { + return nil, errors.New("ebook not found") + } + + // Check if purchase exists and belongs to user + purchase, err := _i.EbookPurchasesRepo.FindOne(req.PurchaseId) + if err != nil { + return nil, errors.New("purchase not found") + } + + if purchase.BuyerId != user.ID { + return nil, errors.New("purchase does not belong to user") + } + + if purchase.EbookId != req.EbookId { + return nil, errors.New("purchase does not match ebook") + } + + if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" { + return nil, errors.New("payment not completed") + } + + // Check if user already rated this ebook + existingRating, err := _i.Repo.FindByUserAndEbook(user.ID, req.EbookId) + if err == nil && existingRating != nil { + return nil, errors.New("user already rated this ebook") + } + + // Check if user already rated this purchase + existingPurchaseRating, err := _i.Repo.FindByPurchaseId(req.PurchaseId) + if err == nil && existingPurchaseRating != nil { + return nil, errors.New("user already rated this purchase") + } + + // Create rating + ratingEntity := req.ToEntity() + ratingEntity.UserId = user.ID + + ratingData, err := _i.Repo.Create(ratingEntity) + if err != nil { + return nil, err + } + + // Update ebook rating stats + err = _i.updateEbookRatingStats(req.EbookId) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update ebook rating stats") + } + + rating = mapper.ToEbookRatingsResponse(ratingData) + + return rating, nil +} + +// Update implement interface of EbookRatingsService +func (_i *ebookRatingsService) Update(id uint, req request.EbookRatingsUpdateRequest, authToken string) (err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found") + } + + // Check if rating exists and belongs to user + existingRating, err := _i.Repo.FindOne(id) + if err != nil { + return errors.New("rating not found") + } + + if existingRating.UserId != user.ID { + return errors.New("rating does not belong to user") + } + + // Update rating + ratingEntity := req.ToEntity() + err = _i.Repo.Update(id, ratingEntity) + if err != nil { + return err + } + + // Update ebook rating stats + err = _i.updateEbookRatingStats(existingRating.EbookId) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update ebook rating stats") + } + + return nil +} + +// Delete implement interface of EbookRatingsService +func (_i *ebookRatingsService) Delete(id uint, authToken string) error { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found") + } + + // Check if rating exists and belongs to user + existingRating, err := _i.Repo.FindOne(id) + if err != nil { + return errors.New("rating not found") + } + + if existingRating.UserId != user.ID { + return errors.New("rating does not belong to user") + } + + // Delete rating + err = _i.Repo.Delete(id) + if err != nil { + return err + } + + // Update ebook rating stats + err = _i.updateEbookRatingStats(existingRating.EbookId) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update ebook rating stats") + } + + return nil +} + +// GetRatingStats implement interface of EbookRatingsService +func (_i *ebookRatingsService) GetRatingStats(ebookId uint) (stats *response.EbookRatingStatsResponse, err error) { + totalRatings, averageRating, ratingCounts, err := _i.Repo.GetEbookRatingStats(ebookId) + if err != nil { + return nil, err + } + + stats = mapper.ToEbookRatingStatsResponse(totalRatings, averageRating, ratingCounts) + + return stats, nil +} + +// updateEbookRatingStats updates the rating statistics in the ebook table +func (_i *ebookRatingsService) updateEbookRatingStats(ebookId uint) error { + totalRatings, averageRating, _, err := _i.Repo.GetEbookRatingStats(ebookId) + if err != nil { + return err + } + + // Update ebook with new rating stats + ebookUpdate := &entity.Ebooks{ + Rating: &averageRating, + ReviewCount: &totalRatings, + } + + err = _i.EbooksRepo.UpdateSkipNull(ebookId, ebookUpdate) + if err != nil { + return err + } + + return nil +} diff --git a/app/module/ebooks/service/ebook_wishlists.service.go b/app/module/ebooks/service/ebook_wishlists.service.go new file mode 100644 index 0000000..65780a6 --- /dev/null +++ b/app/module/ebooks/service/ebook_wishlists.service.go @@ -0,0 +1,138 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ebooks/mapper" + "campaign-pool-be/app/module/ebooks/repository" + "campaign-pool-be/app/module/ebooks/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + + "github.com/rs/zerolog" +) + +// EbookWishlistsService +type ebookWishlistsService struct { + Repo repository.EbookWishlistsRepository + EbooksRepo repository.EbooksRepository + Log zerolog.Logger + UsersRepo usersRepository.UsersRepository +} + +// EbookWishlistsService define interface of IEbookWishlistsService +type EbookWishlistsService interface { + GetByUserId(authToken string, pagination *paginator.Pagination) (wishlists []*response.EbookWishlistResponse, paging paginator.Pagination, err error) + AddToWishlist(authToken string, ebookId uint) (err error) + RemoveFromWishlist(authToken string, ebookId uint) (err error) + IsInWishlist(authToken string, ebookId uint) (isInWishlist bool, err error) +} + +// NewEbookWishlistsService init EbookWishlistsService +func NewEbookWishlistsService( + repo repository.EbookWishlistsRepository, + ebooksRepo repository.EbooksRepository, + log zerolog.Logger, + usersRepo usersRepository.UsersRepository) EbookWishlistsService { + + return &ebookWishlistsService{ + Repo: repo, + EbooksRepo: ebooksRepo, + Log: log, + UsersRepo: usersRepo, + } +} + +// GetByUserId implement interface of EbookWishlistsService +func (_i *ebookWishlistsService) GetByUserId(authToken string, pagination *paginator.Pagination) (wishlists []*response.EbookWishlistResponse, paging paginator.Pagination, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, paginator.Pagination{}, errors.New("user not found") + } + + wishlistsData, paging, err := _i.Repo.GetByUserId(user.ID, pagination) + if err != nil { + return nil, paging, err + } + + wishlists = mapper.ToEbookWishlistResponseList(wishlistsData) + + return wishlists, paging, nil +} + +// AddToWishlist implement interface of EbookWishlistsService +func (_i *ebookWishlistsService) AddToWishlist(authToken string, ebookId uint) (err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found") + } + + // Check if ebook exists + _, err = _i.EbooksRepo.FindOne(ebookId) + if err != nil { + return errors.New("ebook not found") + } + + // Check if already in wishlist + existingWishlist, err := _i.Repo.FindByUserAndEbook(user.ID, ebookId) + if err == nil && existingWishlist != nil { + return errors.New("ebook already in wishlist") + } + + // Add to wishlist + wishlist := &entity.EbookWishlists{ + UserId: user.ID, + EbookId: ebookId, + } + + _, err = _i.Repo.Create(wishlist) + if err != nil { + return err + } + + // Update wishlist count + err = _i.EbooksRepo.UpdateWishlistCount(ebookId) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update wishlist count") + } + + return nil +} + +// RemoveFromWishlist implement interface of EbookWishlistsService +func (_i *ebookWishlistsService) RemoveFromWishlist(authToken string, ebookId uint) (err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found") + } + + // Check if in wishlist + existingWishlist, err := _i.Repo.FindByUserAndEbook(user.ID, ebookId) + if err != nil || existingWishlist == nil { + return errors.New("ebook not in wishlist") + } + + // Remove from wishlist + err = _i.Repo.Delete(existingWishlist.ID) + if err != nil { + return err + } + + return nil +} + +// IsInWishlist implement interface of EbookWishlistsService +func (_i *ebookWishlistsService) IsInWishlist(authToken string, ebookId uint) (isInWishlist bool, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return false, errors.New("user not found") + } + + existingWishlist, err := _i.Repo.FindByUserAndEbook(user.ID, ebookId) + if err != nil { + return false, nil // Not in wishlist + } + + return existingWishlist != nil, nil +} diff --git a/app/module/ebooks/service/ebooks.service.go b/app/module/ebooks/service/ebooks.service.go new file mode 100644 index 0000000..2b220c4 --- /dev/null +++ b/app/module/ebooks/service/ebooks.service.go @@ -0,0 +1,365 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/ebooks/mapper" + "campaign-pool-be/app/module/ebooks/repository" + "campaign-pool-be/app/module/ebooks/request" + "campaign-pool-be/app/module/ebooks/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "context" + "errors" + "fmt" + "io" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// EbooksService +type ebooksService struct { + Repo repository.EbooksRepository + WishlistRepo repository.EbookWishlistsRepository + PurchaseRepo repository.EbookPurchasesRepository + Log zerolog.Logger + Cfg *config.Config + UsersRepo usersRepository.UsersRepository + MinioStorage *minioStorage.MinioStorage +} + +// EbooksService define interface of IEbooksService +type EbooksService interface { + All(req request.EbooksQueryRequest) (ebooks []*response.EbooksResponse, paging paginator.Pagination, err error) + Show(id uint) (ebook *response.EbooksResponse, err error) + ShowBySlug(slug string) (ebook *response.EbooksResponse, err error) + Save(req request.EbooksCreateRequest, authToken string) (ebook *entity.Ebooks, err error) + SavePdfFile(c *fiber.Ctx, ebookId uint) (err error) + SaveThumbnail(c *fiber.Ctx, ebookId uint) (err error) + Update(id uint, req request.EbooksUpdateRequest) (err error) + Delete(id uint) error + SummaryStats(authToken string) (summaryStats *response.EbookSummaryStats, err error) + DownloadPdf(c *fiber.Ctx, ebookId uint) error +} + +// NewEbooksService init EbooksService +func NewEbooksService( + repo repository.EbooksRepository, + wishlistRepo repository.EbookWishlistsRepository, + purchaseRepo repository.EbookPurchasesRepository, + log zerolog.Logger, + cfg *config.Config, + usersRepo usersRepository.UsersRepository, + minioStorage *minioStorage.MinioStorage) EbooksService { + + return &ebooksService{ + Repo: repo, + WishlistRepo: wishlistRepo, + PurchaseRepo: purchaseRepo, + Log: log, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Cfg: cfg, + } +} + +// All implement interface of EbooksService +func (_i *ebooksService) All(req request.EbooksQueryRequest) (ebooks []*response.EbooksResponse, paging paginator.Pagination, err error) { + ebooksData, paging, err := _i.Repo.GetAll(req) + if err != nil { + return nil, paging, err + } + + ebooks = mapper.ToEbooksResponseList(ebooksData) + + return ebooks, paging, nil +} + +// Show implement interface of EbooksService +func (_i *ebooksService) Show(id uint) (ebook *response.EbooksResponse, err error) { + ebookData, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + ebook = mapper.ToEbooksResponse(ebookData) + + return ebook, nil +} + +// ShowBySlug implement interface of EbooksService +func (_i *ebooksService) ShowBySlug(slug string) (ebook *response.EbooksResponse, err error) { + ebookData, err := _i.Repo.FindBySlug(slug) + if err != nil { + return nil, err + } + + ebook = mapper.ToEbooksResponse(ebookData) + + return ebook, nil +} + +// Save implement interface of EbooksService +func (_i *ebooksService) Save(req request.EbooksCreateRequest, authToken string) (ebook *entity.Ebooks, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, errors.New("user not found") + } + + ebookEntity := req.ToEntity() + ebookEntity.AuthorId = user.ID + ebookEntity.CreatedById = &user.ID + + ebookData, err := _i.Repo.Create(ebookEntity) + if err != nil { + return nil, err + } + + return ebookData, nil +} + +// SavePdfFile implement interface of EbooksService +func (_i *ebooksService) SavePdfFile(c *fiber.Ctx, ebookId uint) (err error) { + // Get the uploaded file + file, err := c.FormFile("file") + if err != nil { + return errors.New("file is required") + } + + // Validate file type + contentType := file.Header.Get("Content-Type") + if contentType != "application/pdf" { + return errors.New("only PDF files are allowed") + } + + // Validate file size (max 50MB) + if file.Size > 50*1024*1024 { + return errors.New("file size must be less than 50MB") + } + + // Generate unique filename + ext := filepath.Ext(file.Filename) + filename := fmt.Sprintf("ebook_%d_%d%s", ebookId, time.Now().Unix(), ext) + + // Upload to MinIO + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + return err + } + + objectName := fmt.Sprintf("ebooks/pdfs/%s", filename) + _, err = minioClient.PutObject( + context.Background(), + bucketName, + objectName, + fileReader, + file.Size, + minio.PutObjectOptions{ + ContentType: contentType, + }, + ) + if err != nil { + return err + } + + // Update ebook record with file info + ebookUpdate := &entity.Ebooks{ + PdfFilePath: &objectName, + PdfFileName: &filename, + PdfFileSize: &file.Size, + } + + err = _i.Repo.UpdateSkipNull(ebookId, ebookUpdate) + if err != nil { + return err + } + + return nil +} + +// SaveThumbnail implement interface of EbooksService +func (_i *ebooksService) SaveThumbnail(c *fiber.Ctx, ebookId uint) (err error) { + // Get the uploaded file + file, err := c.FormFile("file") + if err != nil { + return errors.New("file is required") + } + + // Validate file type + contentType := file.Header.Get("Content-Type") + if !strings.HasPrefix(contentType, "image/") { + return errors.New("only image files are allowed") + } + + // Validate file size (max 5MB) + if file.Size > 5*1024*1024 { + return errors.New("file size must be less than 5MB") + } + + // Generate unique filename + ext := filepath.Ext(file.Filename) + filename := fmt.Sprintf("ebook_thumbnail_%d_%d%s", ebookId, time.Now().Unix(), ext) + + // Upload to MinIO + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + return err + } + + objectName := fmt.Sprintf("ebooks/thumbnails/%s", filename) + _, err = minioClient.PutObject( + context.Background(), + bucketName, + objectName, + fileReader, + file.Size, + minio.PutObjectOptions{ + ContentType: contentType, + }, + ) + if err != nil { + return err + } + + // Update ebook record with thumbnail info + ebookUpdate := &entity.Ebooks{ + ThumbnailPath: &objectName, + ThumbnailName: &filename, + } + + err = _i.Repo.UpdateSkipNull(ebookId, ebookUpdate) + if err != nil { + return err + } + + return nil +} + +// Update implement interface of EbooksService +func (_i *ebooksService) Update(id uint, req request.EbooksUpdateRequest) (err error) { + ebookEntity := req.ToEntity() + err = _i.Repo.Update(id, ebookEntity) + if err != nil { + return err + } + + return nil +} + +// Delete implement interface of EbooksService +func (_i *ebooksService) Delete(id uint) error { + err := _i.Repo.Delete(id) + if err != nil { + return err + } + + return nil +} + +// SummaryStats implement interface of EbooksService +func (_i *ebooksService) SummaryStats(authToken string) (summaryStats *response.EbookSummaryStats, err error) { + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return nil, errors.New("user not found") + } + + summaryStats, err = _i.Repo.SummaryStats(user.ID) + if err != nil { + return nil, err + } + + return summaryStats, nil +} + +// DownloadPdf implement interface of EbooksService +func (_i *ebooksService) DownloadPdf(c *fiber.Ctx, ebookId uint) error { + // Get ebook data + ebook, err := _i.Repo.FindOne(ebookId) + if err != nil { + return err + } + + if ebook.PdfFilePath == nil { + return errors.New("PDF file not found") + } + + // Check if user has purchased this ebook + authToken := c.Get("Authorization") + user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if user == nil { + return errors.New("user not found") + } + + purchase, err := _i.PurchaseRepo.FindByBuyerAndEbook(user.ID, ebookId) + if err != nil { + return errors.New("you must purchase this ebook before downloading") + } + + if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" { + return errors.New("payment not completed") + } + + // Get file from MinIO + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + return err + } + + object, err := minioClient.GetObject( + context.Background(), + bucketName, + *ebook.PdfFilePath, + minio.GetObjectOptions{}, + ) + if err != nil { + return err + } + defer object.Close() + + // Read file content + fileContent, err := io.ReadAll(object) + if err != nil { + return err + } + + // Update download count + err = _i.Repo.UpdateDownloadCount(ebookId) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update download count") + } + + err = _i.PurchaseRepo.UpdateDownloadCount(purchase.ID) + if err != nil { + _i.Log.Error().Err(err).Msg("Failed to update purchase download count") + } + + // Set response headers + c.Set("Content-Type", "application/pdf") + c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *ebook.PdfFileName)) + c.Set("Content-Length", strconv.Itoa(len(fileContent))) + + return c.Send(fileContent) +} diff --git a/app/module/education_history/controller/controller.go b/app/module/education_history/controller/controller.go new file mode 100644 index 0000000..0ceac24 --- /dev/null +++ b/app/module/education_history/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/education_history/service" + +type Controller struct { + EducationHistory EducationHistoryController +} + +func NewController(EducationHistoryService service.EducationHistoryService) *Controller { + return &Controller{ + EducationHistory: NewEducationHistoryController(EducationHistoryService), + } +} diff --git a/app/module/education_history/controller/education_history.controller.go b/app/module/education_history/controller/education_history.controller.go new file mode 100644 index 0000000..24abe80 --- /dev/null +++ b/app/module/education_history/controller/education_history.controller.go @@ -0,0 +1,257 @@ +package controller + +import ( + "campaign-pool-be/app/module/education_history/request" + "campaign-pool-be/app/module/education_history/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type educationHistoryController struct { + educationHistoryService service.EducationHistoryService +} + +type EducationHistoryController 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 + UploadCertificate(c *fiber.Ctx) error +} + +func NewEducationHistoryController(educationHistoryService service.EducationHistoryService) EducationHistoryController { + return &educationHistoryController{ + educationHistoryService: educationHistoryService, + } +} + +// All Education History +// @Summary Get all Education History +// @Description API for getting all Education History for specific user +// @Tags Education History +// @Param req query request.EducationHistoryQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /education-history [get] +func (_i *educationHistoryController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + reqContext := request.EducationHistoryQueryRequestContext{ + UserID: c.Query("userId"), + SchoolName: c.Query("schoolName"), + Major: c.Query("major"), + EducationLevel: c.Query("educationLevel"), + GraduationYear: c.Query("graduationYear"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + educationData, paging, err := _i.educationHistoryService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Education history list successfully retrieved"}, + Data: educationData, + Meta: paging, + }) +} + +// Show Education History +// @Summary Get one Education History +// @Description API for getting one Education History +// @Tags Education History +// @Param id path int true "Education History ID" +// @Param userId query uint true "User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /education-history/{id} [get] +func (_i *educationHistoryController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + educationData, err := _i.educationHistoryService.Show(uint(userId), uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Education history successfully retrieved"}, + Data: educationData, + }) +} + +// Save Education History +// @Summary Create Education History +// @Description API for create Education History +// @Tags Education History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param userId query uint true "User ID" +// @Param payload body request.EducationHistoryCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /education-history [post] +func (_i *educationHistoryController) Save(c *fiber.Ctx) error { + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + req := new(request.EducationHistoryCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.educationHistoryService.Save(uint(userId), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Education history successfully created"}, + Data: dataResult, + }) +} + +// Update Education History +// @Summary Update Education History +// @Description API for update Education History +// @Tags Education History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Education History ID" +// @Param userId query uint true "User ID" +// @Param payload body request.EducationHistoryUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /education-history/{id} [put] +func (_i *educationHistoryController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + req := new(request.EducationHistoryUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.educationHistoryService.Update(uint(userId), uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Education history successfully updated"}, + }) +} + +// Delete Education History +// @Summary Delete Education History +// @Description API for delete Education History +// @Tags Education History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Education History ID" +// @Param userId query uint true "User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /education-history/{id} [delete] +func (_i *educationHistoryController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + err = _i.educationHistoryService.Delete(uint(userId), uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Education history successfully deleted"}, + }) +} + +// UploadCertificate Education History +// @Summary Upload Certificate for Education History +// @Description API for upload certificate image for Education History +// @Tags Education History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Education History ID" +// @Param userId query uint true "User ID" +// @Param certificate formData file true "Certificate image file" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /education-history/{id}/certificate [post] +func (_i *educationHistoryController) UploadCertificate(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + err = _i.educationHistoryService.UploadCertificate(uint(userId), uint(id), c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Certificate successfully uploaded"}, + }) +} diff --git a/app/module/education_history/education_history.module.go b/app/module/education_history/education_history.module.go new file mode 100644 index 0000000..ff4022d --- /dev/null +++ b/app/module/education_history/education_history.module.go @@ -0,0 +1,55 @@ +package education_history + +import ( + "campaign-pool-be/app/module/education_history/controller" + "campaign-pool-be/app/module/education_history/repository" + "campaign-pool-be/app/module/education_history/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of EducationHistoryRouter +type EducationHistoryRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of EducationHistory module +var NewEducationHistoryModule = fx.Options( + // register repository of EducationHistory module + fx.Provide(repository.NewEducationHistoryRepository), + + // register service of EducationHistory module + fx.Provide(service.NewEducationHistoryService), + + // register controller of EducationHistory module + fx.Provide(controller.NewController), + + // register router of EducationHistory module + fx.Provide(NewEducationHistoryRouter), +) + +// init EducationHistoryRouter +func NewEducationHistoryRouter(fiber *fiber.App, controller *controller.Controller) *EducationHistoryRouter { + return &EducationHistoryRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of EducationHistory module +func (_i *EducationHistoryRouter) RegisterEducationHistoryRoutes() { + // define controllers + educationHistoryController := _i.Controller.EducationHistory + + // define routes + _i.App.Route("/education-history", func(router fiber.Router) { + router.Get("/", educationHistoryController.All) + router.Get("/:id", educationHistoryController.Show) + router.Post("/", educationHistoryController.Save) + router.Put("/:id", educationHistoryController.Update) + router.Delete("/:id", educationHistoryController.Delete) + router.Post("/:id/certificate", educationHistoryController.UploadCertificate) + }) +} diff --git a/app/module/education_history/mapper/education_history.mapper.go b/app/module/education_history/mapper/education_history.mapper.go new file mode 100644 index 0000000..9f3c6d6 --- /dev/null +++ b/app/module/education_history/mapper/education_history.mapper.go @@ -0,0 +1,31 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/education_history/response" +) + +func EducationHistoryResponseMapper(educationHistory *entity.EducationHistory) *response.EducationHistoryResponse { + result := &response.EducationHistoryResponse{ + ID: educationHistory.ID, + UserID: educationHistory.UserID, + SchoolName: educationHistory.SchoolName, + Major: educationHistory.Major, + EducationLevel: educationHistory.EducationLevel, + GraduationYear: educationHistory.GraduationYear, + CertificateImage: educationHistory.CertificateImage, + CreatedAt: educationHistory.CreatedAt, + UpdatedAt: educationHistory.UpdatedAt, + } + + if educationHistory.User != nil { + result.User = &response.UserBasicInfo{ + ID: educationHistory.User.ID, + Username: educationHistory.User.Username, + Fullname: educationHistory.User.Fullname, + Email: educationHistory.User.Email, + } + } + + return result +} diff --git a/app/module/education_history/repository/education_history.repository.go b/app/module/education_history/repository/education_history.repository.go new file mode 100644 index 0000000..4245fe3 --- /dev/null +++ b/app/module/education_history/repository/education_history.repository.go @@ -0,0 +1,87 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/education_history/request" + "campaign-pool-be/utils/paginator" +) + +type educationHistoryRepository struct { + DB *database.Database +} + +type EducationHistoryRepository interface { + GetAll(userId uint, req request.EducationHistoryQueryRequest) (educationHistories []*entity.EducationHistory, paging paginator.Pagination, err error) + FindOneByUserAndId(userId uint, id uint) (educationHistory *entity.EducationHistory, err error) + Create(educationHistory *entity.EducationHistory) (result *entity.EducationHistory, err error) + Update(userId uint, id uint, educationHistory *entity.EducationHistory) (err error) + Delete(userId uint, id uint) (err error) +} + +func NewEducationHistoryRepository(db *database.Database) EducationHistoryRepository { + return &educationHistoryRepository{ + DB: db, + } +} + +func (_i *educationHistoryRepository) GetAll(userId uint, req request.EducationHistoryQueryRequest) (educationHistories []*entity.EducationHistory, paging paginator.Pagination, err error) { + query := _i.DB.DB.Where("user_id = ?", userId) + + // Apply filters + if req.SchoolName != nil { + query = query.Where("school_name ILIKE ?", "%"+*req.SchoolName+"%") + } + if req.Major != nil { + query = query.Where("major ILIKE ?", "%"+*req.Major+"%") + } + if req.EducationLevel != nil { + query = query.Where("education_level = ?", *req.EducationLevel) + } + if req.GraduationYear != nil { + query = query.Where("graduation_year = ?", *req.GraduationYear) + } + + // Include user relationship + query = query.Preload("User") + + // Order by graduation year desc + query = query.Order("graduation_year 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(&educationHistories).Error + paging = *req.Pagination + + return +} + +func (_i *educationHistoryRepository) FindOneByUserAndId(userId uint, id uint) (educationHistory *entity.EducationHistory, err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Preload("User").First(&educationHistory).Error + return +} + +func (_i *educationHistoryRepository) Create(educationHistory *entity.EducationHistory) (result *entity.EducationHistory, err error) { + err = _i.DB.DB.Create(educationHistory).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Preload("User").First(&result, educationHistory.ID).Error + return +} + +func (_i *educationHistoryRepository) Update(userId uint, id uint, educationHistory *entity.EducationHistory) (err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Updates(educationHistory).Error + return +} + +func (_i *educationHistoryRepository) Delete(userId uint, id uint) (err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Delete(&entity.EducationHistory{}).Error + return +} diff --git a/app/module/education_history/request/education_history.request.go b/app/module/education_history/request/education_history.request.go new file mode 100644 index 0000000..f56280d --- /dev/null +++ b/app/module/education_history/request/education_history.request.go @@ -0,0 +1,100 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" +) + +type EducationHistoryQueryRequest struct { + UserID uint `json:"userId" validate:"required"` + SchoolName *string `json:"schoolName"` + Major *string `json:"major"` + EducationLevel *string `json:"educationLevel"` + GraduationYear *int `json:"graduationYear"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type EducationHistoryCreateRequest struct { + SchoolName string `json:"schoolName" validate:"required,min=2,max=255"` + Major string `json:"major" validate:"required,min=2,max=255"` + EducationLevel string `json:"educationLevel" validate:"required,min=2,max=100"` + GraduationYear int `json:"graduationYear" validate:"required,min=1950,max=2030"` + CertificateImage string `json:"certificateImage,omitempty"` +} + +func (req EducationHistoryCreateRequest) ToEntity(userId uint) *entity.EducationHistory { + certificateImage := &req.CertificateImage + if req.CertificateImage == "" { + certificateImage = nil + } + + return &entity.EducationHistory{ + UserID: userId, + SchoolName: req.SchoolName, + Major: req.Major, + EducationLevel: req.EducationLevel, + GraduationYear: req.GraduationYear, + CertificateImage: certificateImage, + } +} + +type EducationHistoryUpdateRequest struct { + SchoolName string `json:"schoolName" validate:"required,min=2,max=255"` + Major string `json:"major" validate:"required,min=2,max=255"` + EducationLevel string `json:"educationLevel" validate:"required,min=2,max=100"` + GraduationYear int `json:"graduationYear" validate:"required,min=1950,max=2030"` + CertificateImage string `json:"certificateImage,omitempty"` +} + +func (req EducationHistoryUpdateRequest) ToEntity(userId uint) *entity.EducationHistory { + certificateImage := &req.CertificateImage + if req.CertificateImage == "" { + certificateImage = nil + } + + return &entity.EducationHistory{ + UserID: userId, + SchoolName: req.SchoolName, + Major: req.Major, + EducationLevel: req.EducationLevel, + GraduationYear: req.GraduationYear, + CertificateImage: certificateImage, + } +} + +type EducationHistoryQueryRequestContext struct { + UserID string `json:"userId"` + SchoolName string `json:"schoolName"` + Major string `json:"major"` + EducationLevel string `json:"educationLevel"` + GraduationYear string `json:"graduationYear"` +} + +func (req EducationHistoryQueryRequestContext) ToParamRequest() EducationHistoryQueryRequest { + var request EducationHistoryQueryRequest + + if userId := req.UserID; userId != "" { + userIdUint, err := strconv.ParseUint(userId, 10, 0) + if err == nil { + request.UserID = uint(userIdUint) + } + } + if schoolName := req.SchoolName; schoolName != "" { + request.SchoolName = &schoolName + } + if major := req.Major; major != "" { + request.Major = &major + } + if educationLevel := req.EducationLevel; educationLevel != "" { + request.EducationLevel = &educationLevel + } + if graduationYearStr := req.GraduationYear; graduationYearStr != "" { + graduationYear, err := strconv.Atoi(graduationYearStr) + if err == nil { + request.GraduationYear = &graduationYear + } + } + + return request +} diff --git a/app/module/education_history/response/education_history.response.go b/app/module/education_history/response/education_history.response.go new file mode 100644 index 0000000..a6940c5 --- /dev/null +++ b/app/module/education_history/response/education_history.response.go @@ -0,0 +1,25 @@ +package response + +import ( + "time" +) + +type EducationHistoryResponse struct { + ID uint `json:"id"` + UserID uint `json:"userId"` + SchoolName string `json:"schoolName"` + Major string `json:"major"` + EducationLevel string `json:"educationLevel"` + GraduationYear int `json:"graduationYear"` + CertificateImage *string `json:"certificateImage"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + User *UserBasicInfo `json:"user,omitempty"` +} + +type UserBasicInfo struct { + ID uint `json:"id"` + Username string `json:"username"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} diff --git a/app/module/education_history/service/education_history.service.go b/app/module/education_history/service/education_history.service.go new file mode 100644 index 0000000..d38a355 --- /dev/null +++ b/app/module/education_history/service/education_history.service.go @@ -0,0 +1,186 @@ +package service + +import ( + "context" + "errors" + "fmt" + "math/rand" + "path/filepath" + "strconv" + "strings" + "time" + + "campaign-pool-be/app/module/education_history/mapper" + "campaign-pool-be/app/module/education_history/repository" + "campaign-pool-be/app/module/education_history/request" + "campaign-pool-be/app/module/education_history/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +type educationHistoryService struct { + Repo repository.EducationHistoryRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage +} + +type EducationHistoryService interface { + All(req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) + Show(userId uint, id uint) (educationHistory *response.EducationHistoryResponse, err error) + Save(userId uint, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) + Update(userId uint, id uint, req request.EducationHistoryUpdateRequest) (err error) + Delete(userId uint, id uint) error + UploadCertificate(userId uint, id uint, c *fiber.Ctx) error +} + +func NewEducationHistoryService(repo repository.EducationHistoryRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) EducationHistoryService { + return &educationHistoryService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + } +} + +func (_i *educationHistoryService) All(req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req.UserID, req) + if err != nil { + return + } + + for _, result := range results { + educationHistories = append(educationHistories, mapper.EducationHistoryResponseMapper(result)) + } + + return +} + +func (_i *educationHistoryService) Show(userId uint, id uint) (educationHistory *response.EducationHistoryResponse, err error) { + result, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return nil, err + } + + return mapper.EducationHistoryResponseMapper(result), nil +} + +func (_i *educationHistoryService) Save(userId uint, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) { + _i.Log.Info().Interface("data", req).Msg("Creating education history") + + entity := req.ToEntity(userId) + + result, err := _i.Repo.Create(entity) + if err != nil { + return nil, err + } + + return mapper.EducationHistoryResponseMapper(result), nil +} + +func (_i *educationHistoryService) Update(userId uint, id uint, req request.EducationHistoryUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("Updating education history") + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("education history not found") + } + + entity := req.ToEntity(userId) + return _i.Repo.Update(userId, id, entity) +} + +func (_i *educationHistoryService) Delete(userId uint, id uint) error { + _i.Log.Info().Uint("userId", userId).Uint("id", id).Msg("Deleting education history") + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("education history not found") + } + + return _i.Repo.Delete(userId, id) +} + +func (_i *educationHistoryService) UploadCertificate(userId uint, id uint, c *fiber.Ctx) error { + _i.Log.Info().Uint("userId", userId).Uint("id", id).Msg("Uploading certificate") + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("education history not found") + } + + // Get multipart form + form, err := c.MultipartForm() + if err != nil { + return err + } + + // Get file from form + files := form.File["certificate"] + if len(files) == 0 { + return errors.New("no certificate file provided") + } + + fileHeader := files[0] + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + // Create minio connection + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + // Process file + 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("education-history/certificates/%d/%d/%s", now.Year(), now.Month(), newFilename) + + // Upload file to MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + + // Update certificate image path with MinIO object name + existing.CertificateImage = &objectName + return _i.Repo.Update(userId, id, existing) +} diff --git a/app/module/feedbacks/controller/controller.go b/app/module/feedbacks/controller/controller.go new file mode 100644 index 0000000..93abc33 --- /dev/null +++ b/app/module/feedbacks/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/feedbacks/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + Feedbacks FeedbacksController +} + +func NewController(FeedbacksService service.FeedbacksService, log zerolog.Logger) *Controller { + return &Controller{ + Feedbacks: NewFeedbacksController(FeedbacksService, log), + } +} diff --git a/app/module/feedbacks/controller/feedbacks.controller.go b/app/module/feedbacks/controller/feedbacks.controller.go new file mode 100644 index 0000000..a91ad88 --- /dev/null +++ b/app/module/feedbacks/controller/feedbacks.controller.go @@ -0,0 +1,232 @@ +package controller + +import ( + "campaign-pool-be/app/module/feedbacks/request" + "campaign-pool-be/app/module/feedbacks/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type feedbacksController struct { + feedbacksService service.FeedbacksService + Log zerolog.Logger +} + +type FeedbacksController 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 + FeedbackMonthlyStats(c *fiber.Ctx) error +} + +func NewFeedbacksController(feedbacksService service.FeedbacksService, log zerolog.Logger) FeedbacksController { + return &feedbacksController{ + feedbacksService: feedbacksService, + Log: log, + } +} + +// All get all Feedbacks +// @Summary Get all Feedbacks +// @Description API for getting all Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param req query request.FeedbacksQueryRequest 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 /feedbacks [get] +func (_i *feedbacksController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.FeedbacksQueryRequestContext{ + Message: c.Query("message"), + CommentFromName: c.Query("commentFromName"), + CommentFromEmail: c.Query("commentFromEmail"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + feedbacksData, paging, err := _i.feedbacksService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks list successfully retrieved"}, + Data: feedbacksData, + Meta: paging, + }) +} + +// Show get one Feedbacks +// @Summary Get one Feedbacks +// @Description API for getting one Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param id path int true "Feedbacks ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/{id} [get] +func (_i *feedbacksController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + feedbacksData, err := _i.feedbacksService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully retrieved"}, + Data: feedbacksData, + }) +} + +// Save create Feedbacks +// @Summary Create Feedbacks +// @Description API for create Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.FeedbacksCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks [post] +func (_i *feedbacksController) Save(c *fiber.Ctx) error { + req := new(request.FeedbacksCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.feedbacksService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully created"}, + Data: dataResult, + }) +} + +// Update update Feedbacks +// @Summary update Feedbacks +// @Description API for update Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.FeedbacksUpdateRequest true "Required payload" +// @Param id path int true "Feedbacks ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/{id} [put] +func (_i *feedbacksController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.FeedbacksUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.feedbacksService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully updated"}, + }) +} + +// Delete delete Feedbacks +// @Summary delete Feedbacks +// @Description API for delete Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Feedbacks ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/{id} [delete] +func (_i *feedbacksController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.feedbacksService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Feedbacks successfully deleted"}, + }) +} + +// FeedbackMonthlyStats Feedbacks +// @Summary FeedbackMonthlyStats Feedbacks +// @Description API for FeedbackMonthlyStats of Feedbacks +// @Tags Feedbacks +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param year query int false "year" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /feedbacks/statistic/monthly [get] +func (_i *feedbacksController) FeedbackMonthlyStats(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + year := c.Query("year") + yearInt, err := strconv.Atoi(year) + if err != nil { + return err + } + + response, err := _i.feedbacksService.FeedbackMonthlyStats(authToken, &yearInt) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"FeedbacksMonthlyStats of Feedbacks successfully retrieved"}, + Data: response, + }) +} diff --git a/app/module/feedbacks/feedbacks.module.go b/app/module/feedbacks/feedbacks.module.go new file mode 100644 index 0000000..c53b58f --- /dev/null +++ b/app/module/feedbacks/feedbacks.module.go @@ -0,0 +1,55 @@ +package feedbacks + +import ( + "campaign-pool-be/app/module/feedbacks/controller" + "campaign-pool-be/app/module/feedbacks/repository" + "campaign-pool-be/app/module/feedbacks/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of FeedbacksRouter +type FeedbacksRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Feedbacks module +var NewFeedbacksModule = fx.Options( + // register repository of Feedbacks module + fx.Provide(repository.NewFeedbacksRepository), + + // register service of Feedbacks module + fx.Provide(service.NewFeedbacksService), + + // register controller of Feedbacks module + fx.Provide(controller.NewController), + + // register router of Feedbacks module + fx.Provide(NewFeedbacksRouter), +) + +// init FeedbacksRouter +func NewFeedbacksRouter(fiber *fiber.App, controller *controller.Controller) *FeedbacksRouter { + return &FeedbacksRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Feedbacks module +func (_i *FeedbacksRouter) RegisterFeedbacksRoutes() { + // define controllers + feedbacksController := _i.Controller.Feedbacks + + // define routes + _i.App.Route("/feedbacks", func(router fiber.Router) { + router.Get("/", feedbacksController.All) + router.Get("/:id", feedbacksController.Show) + router.Post("/", feedbacksController.Save) + router.Put("/:id", feedbacksController.Update) + router.Delete("/:id", feedbacksController.Delete) + router.Get("/statistic/monthly", feedbacksController.FeedbackMonthlyStats) + }) +} diff --git a/app/module/feedbacks/mapper/feedbacks.mapper.go b/app/module/feedbacks/mapper/feedbacks.mapper.go new file mode 100644 index 0000000..2133c99 --- /dev/null +++ b/app/module/feedbacks/mapper/feedbacks.mapper.go @@ -0,0 +1,24 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/feedbacks/response" +) + +func FeedbacksResponseMapper(feedbacksReq *entity.Feedbacks) (feedbacksRes *res.FeedbacksResponse) { + if feedbacksReq != nil { + feedbacksRes = &res.FeedbacksResponse{ + ID: feedbacksReq.ID, + Message: feedbacksReq.Message, + CommentFromName: feedbacksReq.CommentFromName, + CommentFromEmail: feedbacksReq.CommentFromEmail, + StatusId: feedbacksReq.StatusId, + ApprovedAt: feedbacksReq.ApprovedAt, + ReplyMessage: feedbacksReq.ReplyMessage, + IsActive: feedbacksReq.IsActive, + CreatedAt: feedbacksReq.CreatedAt, + UpdatedAt: feedbacksReq.UpdatedAt, + } + } + return feedbacksRes +} diff --git a/app/module/feedbacks/repository/feedbacks.repository.go b/app/module/feedbacks/repository/feedbacks.repository.go new file mode 100644 index 0000000..c6326af --- /dev/null +++ b/app/module/feedbacks/repository/feedbacks.repository.go @@ -0,0 +1,161 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/feedbacks/request" + "campaign-pool-be/app/module/feedbacks/response" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + "time" + + "github.com/rs/zerolog" +) + +type feedbacksRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// FeedbacksRepository define interface of IFeedbacksRepository +type FeedbacksRepository interface { + GetAll(req request.FeedbacksQueryRequest) (feedbackss []*entity.Feedbacks, paging paginator.Pagination, err error) + FindOne(id uint) (feedbacks *entity.Feedbacks, err error) + Create(feedbacks *entity.Feedbacks) (feedbacksReturn *entity.Feedbacks, err error) + Update(id uint, feedbacks *entity.Feedbacks) (err error) + Delete(id uint) (err error) + FeedbacksMonthlyStats(year int) (feedbacksMonthlyStats []*response.FeedbacksMonthlyStats, err error) +} + +func NewFeedbacksRepository(db *database.Database, logger zerolog.Logger) FeedbacksRepository { + return &feedbacksRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of IFeedbacksRepository +func (_i *feedbacksRepository) GetAll(req request.FeedbacksQueryRequest) (feedbackss []*entity.Feedbacks, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Feedbacks{}) + + query = query.Where("is_active = ?", true) + + if req.Message != nil && *req.Message != "" { + message := strings.ToLower(*req.Message) + query = query.Where("LOWER(message) LIKE ?", "%"+strings.ToLower(message)+"%") + } + if req.CommentFromName != nil && *req.CommentFromName != "" { + commentFromName := strings.ToLower(*req.CommentFromName) + query = query.Where("LOWER(comment_from_name) LIKE ?", "%"+strings.ToLower(commentFromName)+"%") + } + if req.CommentFromEmail != nil && *req.CommentFromEmail != "" { + commentFromEmail := strings.ToLower(*req.CommentFromEmail) + query = query.Where("LOWER(comment_from_email) LIKE ?", "%"+strings.ToLower(commentFromEmail)+"%") + } + 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(&feedbackss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *feedbacksRepository) FindOne(id uint) (feedbacks *entity.Feedbacks, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.First(&feedbacks).Error; err != nil { + return nil, err + } + + return feedbacks, nil +} + +func (_i *feedbacksRepository) Create(feedbacks *entity.Feedbacks) (feedbacksReturn *entity.Feedbacks, err error) { + result := _i.DB.DB.Create(feedbacks) + return feedbacks, result.Error +} + +func (_i *feedbacksRepository) Update(id uint, feedbacks *entity.Feedbacks) (err error) { + feedbacksMap, err := utilSvc.StructToMap(feedbacks) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Feedbacks{}).Where(&entity.Feedbacks{ID: id}) + return query.Updates(feedbacksMap).Error +} + +func (_i *feedbacksRepository) Delete(id uint) error { + query := _i.DB.DB.Model(&entity.Feedbacks{}).Where("id = ?", id) + return query.Delete(&entity.Feedbacks{}).Error +} + +func (_i *feedbacksRepository) FeedbacksMonthlyStats(year int) (feedbacksMonthlyStats []*response.FeedbacksMonthlyStats, err error) { + + if year < 1900 || year > 2100 { + return nil, fmt.Errorf("invalid year") + } + + var results []struct { + Month int + Day int + TotalFeedbacks int + } + + query := _i.DB.DB.Model(&entity.Feedbacks{}). + Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+ + "count(id) as total_feedbacks"). + Where("EXTRACT(YEAR FROM created_at) = ?", year) + + err = query.Group("month, day").Scan(&results).Error + if err != nil { + return nil, err + } + + // Siapkan struktur untuk menyimpan data bulanan + feedbackStats := make([]*response.FeedbacksMonthlyStats, 12) + for i := 0; i < 12; i++ { + daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day() + feedbackStats[i] = &response.FeedbacksMonthlyStats{ + Year: year, + Month: i + 1, + Suggestions: make([]int, daysInMonth), + } + } + + // Isi data dari hasil agregasi + for _, result := range results { + monthIndex := result.Month - 1 + dayIndex := result.Day - 1 + + if monthIndex >= 0 && monthIndex < 12 { + if dayIndex >= 0 && dayIndex < len(feedbackStats[monthIndex].Suggestions) { + feedbackStats[monthIndex].Suggestions[dayIndex] = result.TotalFeedbacks + } + } + } + + return feedbackStats, nil +} diff --git a/app/module/feedbacks/request/feedbacks.request.go b/app/module/feedbacks/request/feedbacks.request.go new file mode 100644 index 0000000..ef364b2 --- /dev/null +++ b/app/module/feedbacks/request/feedbacks.request.go @@ -0,0 +1,84 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type FeedbacksGeneric interface { + ToEntity() +} + +type FeedbacksQueryRequest struct { + Message *string `json:"message"` + CommentFromName *string `json:"commentFromName"` + CommentFromEmail *string `json:"commentFromEmail"` + StartDate *string `json:"startDate"` + EndDate *string `json:"endDate"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type FeedbacksCreateRequest struct { + Message string `json:"message" validate:"required"` + CommentFromName string `json:"commentFromName" validate:"required"` + CommentFromEmail string `json:"commentFromEmail" validate:"required"` +} + +func (req FeedbacksCreateRequest) ToEntity() *entity.Feedbacks { + return &entity.Feedbacks{ + Message: req.Message, + CommentFromName: req.CommentFromName, + CommentFromEmail: req.CommentFromEmail, + StatusId: 0, + IsActive: true, + } +} + +type FeedbacksUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Message string `json:"message" validate:"required"` + CommentFromName string `json:"commentFromName" validate:"required"` + CommentFromEmail string `json:"commentFromEmail" validate:"required"` +} + +func (req FeedbacksUpdateRequest) ToEntity() *entity.Feedbacks { + return &entity.Feedbacks{ + ID: req.ID, + Message: req.Message, + CommentFromName: req.CommentFromName, + CommentFromEmail: req.CommentFromEmail, + UpdatedAt: time.Now(), + } +} + +type FeedbacksQueryRequestContext struct { + Message string `json:"message"` + CommentFromName string `json:"commentFromName"` + CommentFromEmail string `json:"commentFromEmail"` + StatusId string `json:"statusId"` +} + +func (req FeedbacksQueryRequestContext) ToParamRequest() FeedbacksQueryRequest { + var request FeedbacksQueryRequest + + if message := req.Message; message != "" { + request.Message = &message + } + if commentFromName := req.CommentFromName; commentFromName != "" { + request.CommentFromName = &commentFromName + } + if commentFromEmail := req.CommentFromEmail; commentFromEmail != "" { + request.CommentFromEmail = &commentFromEmail + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/feedbacks/response/feedbacks.response.go b/app/module/feedbacks/response/feedbacks.response.go new file mode 100644 index 0000000..1c72ff4 --- /dev/null +++ b/app/module/feedbacks/response/feedbacks.response.go @@ -0,0 +1,22 @@ +package response + +import "time" + +type FeedbacksResponse struct { + ID uint `json:"id"` + Message string `json:"message"` + CommentFromName string `json:"commentFromName"` + CommentFromEmail string `json:"commentFromEmail"` + StatusId int `json:"statusId"` + ApprovedAt *time.Time `json:"approvedAt"` + ReplyMessage *string `json:"replyMessage"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type FeedbacksMonthlyStats struct { + Year int `json:"year"` + Month int `json:"month"` + Suggestions []int `json:"suggestions"` +} diff --git a/app/module/feedbacks/service/feedbacks.service.go b/app/module/feedbacks/service/feedbacks.service.go new file mode 100644 index 0000000..3a3e82e --- /dev/null +++ b/app/module/feedbacks/service/feedbacks.service.go @@ -0,0 +1,106 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/feedbacks/mapper" + "campaign-pool-be/app/module/feedbacks/repository" + "campaign-pool-be/app/module/feedbacks/request" + "campaign-pool-be/app/module/feedbacks/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// FeedbacksService +type feedbacksService struct { + Repo repository.FeedbacksRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// FeedbacksService define interface of IFeedbacksService +type FeedbacksService interface { + All(req request.FeedbacksQueryRequest) (feedbacks []*response.FeedbacksResponse, paging paginator.Pagination, err error) + Show(id uint) (feedbacks *response.FeedbacksResponse, err error) + Save(req request.FeedbacksCreateRequest, authToken string) (feedbacks *entity.Feedbacks, err error) + Update(id uint, req request.FeedbacksUpdateRequest) (err error) + Delete(id uint) error + FeedbackMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.FeedbacksMonthlyStats, err error) +} + +// NewFeedbacksService init FeedbacksService +func NewFeedbacksService(repo repository.FeedbacksRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) FeedbacksService { + + return &feedbacksService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of FeedbacksService +func (_i *feedbacksService) All(req request.FeedbacksQueryRequest) (feedbackss []*response.FeedbacksResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + feedbackss = append(feedbackss, mapper.FeedbacksResponseMapper(result)) + } + + return +} + +func (_i *feedbacksService) Show(id uint) (feedbacks *response.FeedbacksResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.FeedbacksResponseMapper(result), nil +} + +func (_i *feedbacksService) Save(req request.FeedbacksCreateRequest, authToken string) (feedbacks *entity.Feedbacks, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + entity := req.ToEntity() + + return _i.Repo.Create(entity) +} + +func (_i *feedbacksService) Update(id uint, req request.FeedbacksUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + entity := req.ToEntity() + + return _i.Repo.Update(id, entity) +} + +func (_i *feedbacksService) 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 *feedbacksService) FeedbackMonthlyStats(authToken string, year *int) (articleMonthlyStats []*response.FeedbacksMonthlyStats, err error) { + //user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + + //var userLevelId *uint + //var userLevelNumber *int + // + //if user != nil { + // userLevelId = &user.UserLevelId + // userLevelNumber = &user.UserLevel.LevelNumber + //} + + result, err := _i.Repo.FeedbacksMonthlyStats(*year) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/app/module/magazine_files/controller/controller.go b/app/module/magazine_files/controller/controller.go new file mode 100644 index 0000000..97d020c --- /dev/null +++ b/app/module/magazine_files/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/magazine_files/service" + +type Controller struct { + MagazineFiles MagazineFilesController +} + +func NewController(MagazineFilesService service.MagazineFilesService) *Controller { + return &Controller{ + MagazineFiles: NewMagazineFilesController(MagazineFilesService), + } +} diff --git a/app/module/magazine_files/controller/magazine_files.controller.go b/app/module/magazine_files/controller/magazine_files.controller.go new file mode 100644 index 0000000..8939de8 --- /dev/null +++ b/app/module/magazine_files/controller/magazine_files.controller.go @@ -0,0 +1,209 @@ +package controller + +import ( + "campaign-pool-be/app/module/magazine_files/request" + "campaign-pool-be/app/module/magazine_files/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type magazineFilesController struct { + magazineFilesService service.MagazineFilesService +} + +type MagazineFilesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error +} + +func NewMagazineFilesController(magazineFilesService service.MagazineFilesService) MagazineFilesController { + return &magazineFilesController{ + magazineFilesService: magazineFilesService, + } +} + +// All MagazineFiles +// @Summary Get all MagazineFiles +// @Description API for getting all MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files [get] +func (_i *magazineFilesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MagazineFilesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + MagazineId: c.Query("magazineId"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + magazineFilesData, paging, err := _i.magazineFilesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles list successfully retrieved"}, + Data: magazineFilesData, + Meta: paging, + }) +} + +// Show MagazineFiles +// @Summary Get one MagazineFiles +// @Description API for getting one MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param id path int true "MagazineFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{id} [get] +func (_i *magazineFilesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + magazineFilesData, err := _i.magazineFilesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles successfully retrieved"}, + Data: magazineFilesData, + }) +} + +// Save MagazineFiles +// @Summary Create MagazineFiles +// @Description API for create MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param files formData file true "Upload file" multiple true +// @Param title formData string true "Magazine file title" +// @Param description formData string true "Magazine file description" +// @Param magazineId path int true "Magazine ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{magazineId} [post] +func (_i *magazineFilesController) Save(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("magazineId"), 10, 0) + if err != nil { + return err + } + title := c.Params("title") + description := c.Params("description") + + err = _i.magazineFilesService.Save(c, uint(id), title, description) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MagazineFiles successfully created"}, + }) +} + +// Update MagazineFiles +// @Summary Update MagazineFiles +// @Description API for update MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "MagazineFiles ID" +// @Body request.MagazineFilesUpdateRequest +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{id} [put] +func (_i *magazineFilesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MagazineFilesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.magazineFilesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles successfully updated"}, + }) +} + +// Delete MagazineFiles +// @Summary delete MagazineFiles +// @Description API for delete MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "MagazineFiles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/{id} [delete] +func (_i *magazineFilesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.magazineFilesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MagazineFiles successfully deleted"}, + }) +} + +// Viewer MagazineFiles +// @Summary Create MagazineFiles +// @Description API for create MagazineFiles +// @Tags Magazine Files +// @Security Bearer +// @Param filename path string true "Magazine File Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazine-files/viewer/{filename} [get] +func (_i *magazineFilesController) Viewer(c *fiber.Ctx) error { + return _i.magazineFilesService.Viewer(c) +} diff --git a/app/module/magazine_files/magazine_files.module.go b/app/module/magazine_files/magazine_files.module.go new file mode 100644 index 0000000..610c176 --- /dev/null +++ b/app/module/magazine_files/magazine_files.module.go @@ -0,0 +1,55 @@ +package magazine_files + +import ( + "campaign-pool-be/app/module/magazine_files/controller" + "campaign-pool-be/app/module/magazine_files/repository" + "campaign-pool-be/app/module/magazine_files/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of MagazineFilesRouter +type MagazineFilesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MagazineFiles module +var NewMagazineFilesModule = fx.Options( + // register repository of MagazineFiles module + fx.Provide(repository.NewMagazineFilesRepository), + + // register service of MagazineFiles module + fx.Provide(service.NewMagazineFilesService), + + // register controller of MagazineFiles module + fx.Provide(controller.NewController), + + // register router of MagazineFiles module + fx.Provide(NewMagazineFilesRouter), +) + +// init MagazineFilesRouter +func NewMagazineFilesRouter(fiber *fiber.App, controller *controller.Controller) *MagazineFilesRouter { + return &MagazineFilesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MagazineFiles module +func (_i *MagazineFilesRouter) RegisterMagazineFilesRoutes() { + // define controllers + magazineFilesController := _i.Controller.MagazineFiles + + // define routes + _i.App.Route("/magazine-files", func(router fiber.Router) { + router.Get("/", magazineFilesController.All) + router.Get("/:id", magazineFilesController.Show) + router.Post("/:magazineId", magazineFilesController.Save) + router.Put("/:id", magazineFilesController.Update) + router.Delete("/:id", magazineFilesController.Delete) + router.Get("/viewer/:filename", magazineFilesController.Viewer) + }) +} diff --git a/app/module/magazine_files/mapper/magazine_files.mapper.go b/app/module/magazine_files/mapper/magazine_files.mapper.go new file mode 100644 index 0000000..336d581 --- /dev/null +++ b/app/module/magazine_files/mapper/magazine_files.mapper.go @@ -0,0 +1,37 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/magazine_files/response" +) + +func MagazineFilesResponseMapper(magazineFilesReq *entity.MagazineFiles, host string) (magazineFilesRes *res.MagazineFilesResponse) { + fileUrl := host + "/magazine-files/viewer/" + if magazineFilesReq.FileName != nil { + fileUrl += *magazineFilesReq.FileName + } + + if magazineFilesReq != nil { + magazineFilesRes = &res.MagazineFilesResponse{ + ID: magazineFilesReq.ID, + Title: magazineFilesReq.Title, + Description: magazineFilesReq.Description, + MagazineId: magazineFilesReq.MagazineId, + DownloadCount: magazineFilesReq.DownloadCount, + FilePath: magazineFilesReq.FilePath, + FileUrl: &fileUrl, + FileName: magazineFilesReq.FileName, + FileAlt: magazineFilesReq.FileAlt, + WidthPixel: magazineFilesReq.WidthPixel, + HeightPixel: magazineFilesReq.HeightPixel, + Size: magazineFilesReq.Size, + StatusId: magazineFilesReq.StatusId, + IsPublish: magazineFilesReq.IsPublish, + PublishedAt: magazineFilesReq.PublishedAt, + IsActive: magazineFilesReq.IsActive, + CreatedAt: magazineFilesReq.CreatedAt, + UpdatedAt: magazineFilesReq.UpdatedAt, + } + } + return magazineFilesRes +} diff --git a/app/module/magazine_files/repository/magazine_files.repository.go b/app/module/magazine_files/repository/magazine_files.repository.go new file mode 100644 index 0000000..c9b76fb --- /dev/null +++ b/app/module/magazine_files/repository/magazine_files.repository.go @@ -0,0 +1,121 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/magazine_files/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" +) + +type magazineFilesRepository struct { + DB *database.Database +} + +// MagazineFilesRepository define interface of IMagazineFilesRepository +type MagazineFilesRepository interface { + GetAll(req request.MagazineFilesQueryRequest) (magazineFiless []*entity.MagazineFiles, paging paginator.Pagination, err error) + FindOne(id uint) (magazineFiles *entity.MagazineFiles, err error) + FindByMagazine(magazineId uint) (magazineFiles []*entity.MagazineFiles, err error) + FindByFilename(filename string) (magazineFiles *entity.MagazineFiles, err error) + Create(magazineFiles *entity.MagazineFiles) (err error) + Update(id uint, magazineFiles *entity.MagazineFiles) (err error) + Delete(id uint) (err error) +} + +func NewMagazineFilesRepository(db *database.Database) MagazineFilesRepository { + return &magazineFilesRepository{ + DB: db, + } +} + +// implement interface of IMagazineFilesRepository +func (_i *magazineFilesRepository) GetAll(req request.MagazineFilesQueryRequest) (magazineFiless []*entity.MagazineFiles, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MagazineFiles{}) + 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.MagazineId != nil { + query = query.Where("magazine_id = ?", req.MagazineId) + } + if req.IsPublish != nil { + query = query.Where("is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&magazineFiless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *magazineFilesRepository) FindByMagazine(magazineId uint) (magazineFiles []*entity.MagazineFiles, err error) { + if err := _i.DB.DB.Where("magazine_id = ?", magazineId).Find(&magazineFiles).Error; err != nil { + return nil, err + } + + return magazineFiles, nil +} + +func (_i *magazineFilesRepository) FindByFilename(magazineFilename string) (magazineFiles *entity.MagazineFiles, err error) { + + if err := _i.DB.DB.Where("file_name = ?", magazineFilename).First(&magazineFiles).Error; err != nil { + return nil, err + } + + return magazineFiles, nil +} + +func (_i *magazineFilesRepository) FindOne(id uint) (magazineFiles *entity.MagazineFiles, err error) { + if err := _i.DB.DB.First(&magazineFiles, id).Error; err != nil { + return nil, err + } + + return magazineFiles, nil +} + +func (_i *magazineFilesRepository) Create(magazineFiles *entity.MagazineFiles) (err error) { + return _i.DB.DB.Create(magazineFiles).Error +} + +func (_i *magazineFilesRepository) Update(id uint, magazineFiles *entity.MagazineFiles) (err error) { + magazineFilesMap, err := utilSvc.StructToMap(magazineFiles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.MagazineFiles{}). + Where(&entity.MagazineFiles{ID: id}). + Updates(magazineFilesMap).Error +} + +func (_i *magazineFilesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MagazineFiles{}, id).Error +} diff --git a/app/module/magazine_files/request/magazine_files.request.go b/app/module/magazine_files/request/magazine_files.request.go new file mode 100644 index 0000000..6ec2fa0 --- /dev/null +++ b/app/module/magazine_files/request/magazine_files.request.go @@ -0,0 +1,137 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type MagazineFilesGeneric interface { + ToEntity() +} + +type MagazineFilesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + MagazineId *int `json:"magazineId"` + DownloadCount *int `json:"downloadCount"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive *bool `json:"isActive"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MagazineFilesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + MagazineId uint `json:"magazineId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + DownloadCount *int `json:"downloadCount"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazineFilesCreateRequest) ToEntity() *entity.MagazineFiles { + return &entity.MagazineFiles{ + Title: req.Title, + Description: req.Description, + MagazineId: req.MagazineId, + DownloadCount: req.DownloadCount, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + } +} + +type MagazineFilesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + MagazineId uint `json:"magazineId" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + DownloadCount *int `json:"downloadCount"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazineFilesUpdateRequest) ToEntity() *entity.MagazineFiles { + return &entity.MagazineFiles{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + MagazineId: req.MagazineId, + DownloadCount: req.DownloadCount, + FilePath: req.FilePath, + FileUrl: req.FileUrl, + FileName: req.FileName, + FileAlt: req.FileAlt, + WidthPixel: req.WidthPixel, + HeightPixel: req.HeightPixel, + Size: req.Size, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + } +} + +type MagazineFilesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + MagazineId string `json:"magazineId"` + IsPublish string `json:"isPublish"` + StatusId string `json:"statusId"` +} + +func (req MagazineFilesQueryRequestContext) ToParamRequest() MagazineFilesQueryRequest { + var request MagazineFilesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + if magazineIdStr := req.MagazineId; magazineIdStr != "" { + magazineId, err := strconv.Atoi(magazineIdStr) + if err == nil { + request.MagazineId = &magazineId + } + } + 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 +} diff --git a/app/module/magazine_files/response/magazine_files.response.go b/app/module/magazine_files/response/magazine_files.response.go new file mode 100644 index 0000000..5b46c07 --- /dev/null +++ b/app/module/magazine_files/response/magazine_files.response.go @@ -0,0 +1,24 @@ +package response + +import "time" + +type MagazineFilesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + MagazineId uint `json:"magazineId"` + DownloadCount *int `json:"downloadCount"` + FilePath *string `json:"filePath"` + FileUrl *string `json:"fileUrl"` + FileName *string `json:"fileName"` + FileAlt *string `json:"fileAlt"` + WidthPixel *string `json:"widthPixel"` + HeightPixel *string `json:"heightPixel"` + Size *string `json:"size"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/magazine_files/service/magazine_files.service.go b/app/module/magazine_files/service/magazine_files.service.go new file mode 100644 index 0000000..5f5192f --- /dev/null +++ b/app/module/magazine_files/service/magazine_files.service.go @@ -0,0 +1,228 @@ +package service + +import ( + "campaign-pool-be/app/module/magazine_files/mapper" + "campaign-pool-be/app/module/magazine_files/repository" + "campaign-pool-be/app/module/magazine_files/request" + "campaign-pool-be/app/module/magazine_files/response" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + "context" + "fmt" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// MagazineFilesService +type magazineFilesService struct { + Repo repository.MagazineFilesRepository + Log zerolog.Logger + Cfg *config.Config + MinioStorage *minioStorage.MinioStorage +} + +// MagazineFilesService define interface of IMagazineFilesService +type MagazineFilesService interface { + All(req request.MagazineFilesQueryRequest) (magazineFiles []*response.MagazineFilesResponse, paging paginator.Pagination, err error) + Show(id uint) (magazineFiles *response.MagazineFilesResponse, err error) + Save(c *fiber.Ctx, id uint, title string, description string) (err error) + Update(id uint, req request.MagazineFilesUpdateRequest) (err error) + Delete(id uint) error + Viewer(c *fiber.Ctx) error +} + +// NewMagazineFilesService init MagazineFilesService +func NewMagazineFilesService(repo repository.MagazineFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) MagazineFilesService { + + return &magazineFilesService{ + Repo: repo, + Log: log, + Cfg: cfg, + MinioStorage: minioStorage, + } +} + +// All implement interface of MagazineFilesService +func (_i *magazineFilesService) All(req request.MagazineFilesQueryRequest) (magazineFiless []*response.MagazineFilesResponse, 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 { + magazineFiless = append(magazineFiless, mapper.MagazineFilesResponseMapper(result, host)) + } + return +} + +func (_i *magazineFilesService) Show(id uint) (magazineFiles *response.MagazineFilesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.MagazineFilesResponseMapper(result, host), nil +} + +func (_i *magazineFilesService) Save(c *fiber.Ctx, id uint, title string, description string) (err error) { + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + + form, err := c.MultipartForm() + + if err != nil { + return err + } + //filess := form.File["files"] + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + for _, files := range form.File { + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: top"). + Interface("files", files).Msg("") + + for _, fileHeader := range files { + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop"). + Interface("data", fileHeader).Msg("") + + src, err := fileHeader.Open() + if err != nil { + return err + } + defer src.Close() + + filename := filepath.Base(fileHeader.Filename) + filenameAlt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(fileHeader.Filename)[1:] + + now := time.Now() + rand.New(rand.NewSource(now.UnixNano())) + randUniqueId := rand.Intn(1000000) + + newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId) + newFilename := newFilenameWithoutExt + "." + extension + objectName := fmt.Sprintf("magazines/upload/%d/%d/%s", now.Year(), now.Month(), newFilename) + + fileSize := strconv.FormatInt(fileHeader.Size, 10) + + req := request.MagazineFilesCreateRequest{ + MagazineId: id, + Title: title, + Description: description, + FilePath: &objectName, + FileName: &newFilename, + FileAlt: &filenameAlt, + Size: &fileSize, + } + + err = _i.Repo.Create(req.ToEntity()) + if err != nil { + return err + } + + // Upload file ke MinIO + _, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{}) + if err != nil { + return err + } + } + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "User:All"). + Interface("data", "Successfully uploaded").Msg("") + + return +} + +func (_i *magazineFilesService) Viewer(c *fiber.Ctx) (err error) { + filename := c.Params("filename") + result, err := _i.Repo.FindByFilename(filename) + if err != nil { + return err + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.FilePath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "Article:Uploads"). + Interface("data", objectName).Msg("") + + // Create minio connection. + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + // Return status 500 and minio connection error. + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + log.Fatalln(err) + } + defer fileContent.Close() + + // Tentukan Content-Type berdasarkan ekstensi file + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback jika tidak ada tipe MIME yang cocok + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} + +func (_i *magazineFilesService) Update(id uint, req request.MagazineFilesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *magazineFilesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/magazines/controller/controller.go b/app/module/magazines/controller/controller.go new file mode 100644 index 0000000..ec251fa --- /dev/null +++ b/app/module/magazines/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/magazines/service" + +type Controller struct { + Magazines MagazinesController +} + +func NewController(MagazinesService service.MagazinesService) *Controller { + return &Controller{ + Magazines: NewMagazinesController(MagazinesService), + } +} diff --git a/app/module/magazines/controller/magazines.controller.go b/app/module/magazines/controller/magazines.controller.go new file mode 100644 index 0000000..020842e --- /dev/null +++ b/app/module/magazines/controller/magazines.controller.go @@ -0,0 +1,237 @@ +package controller + +import ( + "campaign-pool-be/app/module/magazines/request" + "campaign-pool-be/app/module/magazines/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type magazinesController struct { + magazinesService service.MagazinesService +} + +type MagazinesController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + SaveThumbnail(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewMagazinesController(magazinesService service.MagazinesService) MagazinesController { + return &magazinesController{ + magazinesService: magazinesService, + } +} + +// All Magazines +// @Summary Get all Magazines +// @Description API for getting all Magazines +// @Tags Magazines +// @Security Bearer +// @Param req query request.MagazinesQueryRequest 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 /magazines [get] +func (_i *magazinesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MagazinesQueryRequestContext{ + Title: c.Query("title"), + Description: c.Query("description"), + CreatedById: c.Query("createdById"), + StatusId: c.Query("statusId"), + IsPublish: c.Query("isPublish"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + magazinesData, paging, err := _i.magazinesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines list successfully retrieved"}, + Data: magazinesData, + Meta: paging, + }) +} + +// Show Magazines +// @Summary Get one Magazines +// @Description API for getting one Magazines +// @Tags Magazines +// @Security Bearer +// @Param id path int true "Magazines ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/{id} [get] +func (_i *magazinesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + magazinesData, err := _i.magazinesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines successfully retrieved"}, + Data: magazinesData, + }) +} + +// Save Magazines +// @Summary Create Magazines +// @Description API for create Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.MagazinesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines [post] +func (_i *magazinesController) Save(c *fiber.Ctx) error { + req := new(request.MagazinesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.magazinesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Magazines successfully created"}, + Data: dataResult, + }) +} + +// Update Magazines +// @Summary Update Magazines +// @Description API for update Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Magazines ID" +// @Param payload body request.MagazinesUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/{id} [put] +func (_i *magazinesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MagazinesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.magazinesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines successfully updated"}, + }) +} + +// SaveThumbnail Magazines +// @Summary Save Thumbnail Magazines +// @Description API for Save Thumbnail of Magazines +// @Tags Magazines +// @Security Bearer +// @Produce json +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Magazine ID" +// @Param files formData file true "Upload thumbnail" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/thumbnail/{id} [post] +func (_i *magazinesController) SaveThumbnail(c *fiber.Ctx) error { + err := _i.magazinesService.SaveThumbnail(c) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Thumbnail of Magazines successfully created"}, + }) +} + +// Viewer Magazines +// @Summary Viewer Magazines Thumbnail +// @Description API for View Thumbnail of Magazines +// @Tags Magazines +// @Security Bearer +// @Param thumbnailName path string true "Magazines Thumbnail Name" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/thumbnail/viewer/{thumbnailName} [get] +func (_i *magazinesController) Viewer(c *fiber.Ctx) error { + return _i.magazinesService.Viewer(c) +} + +// Delete Magazines +// @Summary Delete Magazines +// @Description API for delete Magazines +// @Tags Magazines +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Magazines ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /magazines/{id} [delete] +func (_i *magazinesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.magazinesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Magazines successfully deleted"}, + }) +} diff --git a/app/module/magazines/magazines.module.go b/app/module/magazines/magazines.module.go new file mode 100644 index 0000000..c7c11c1 --- /dev/null +++ b/app/module/magazines/magazines.module.go @@ -0,0 +1,56 @@ +package magazines + +import ( + "campaign-pool-be/app/module/magazines/controller" + "campaign-pool-be/app/module/magazines/repository" + "campaign-pool-be/app/module/magazines/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of MagazinesRouter +type MagazinesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Magazines module +var NewMagazinesModule = fx.Options( + // register repository of Magazines module + fx.Provide(repository.NewMagazinesRepository), + + // register service of Magazines module + fx.Provide(service.NewMagazinesService), + + // register controller of Magazines module + fx.Provide(controller.NewController), + + // register router of Magazines module + fx.Provide(NewMagazinesRouter), +) + +// init MagazinesRouter +func NewMagazinesRouter(fiber *fiber.App, controller *controller.Controller) *MagazinesRouter { + return &MagazinesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Magazines module +func (_i *MagazinesRouter) RegisterMagazinesRoutes() { + // define controllers + magazinesController := _i.Controller.Magazines + + // define routes + _i.App.Route("/magazines", func(router fiber.Router) { + router.Get("/", magazinesController.All) + router.Get("/:id", magazinesController.Show) + router.Post("/", magazinesController.Save) + router.Put("/:id", magazinesController.Update) + router.Post("/thumbnail/:id", magazinesController.SaveThumbnail) + router.Get("/thumbnail/viewer/:thumbnailName", magazinesController.Viewer) + router.Delete("/:id", magazinesController.Delete) + }) +} diff --git a/app/module/magazines/mapper/magazines.mapper.go b/app/module/magazines/mapper/magazines.mapper.go new file mode 100644 index 0000000..f5b7145 --- /dev/null +++ b/app/module/magazines/mapper/magazines.mapper.go @@ -0,0 +1,42 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + magazineFilesMapper "campaign-pool-be/app/module/magazine_files/mapper" + magazineFilesRepository "campaign-pool-be/app/module/magazine_files/repository" + magazineFilesResponse "campaign-pool-be/app/module/magazine_files/response" + res "campaign-pool-be/app/module/magazines/response" +) + +func MagazinesResponseMapper(magazinesReq *entity.Magazines, magazineFilesRepo magazineFilesRepository.MagazineFilesRepository, host string) (magazinesRes *res.MagazinesResponse) { + magazineFiles, _ := magazineFilesRepo.FindByMagazine(magazinesReq.ID) + var magazineFilesArr []*magazineFilesResponse.MagazineFilesResponse + if magazineFiles != nil && len(magazineFiles) > 0 { + for _, result := range magazineFiles { + magazineFilesArr = append(magazineFilesArr, magazineFilesMapper.MagazineFilesResponseMapper(result, host)) + } + } + + if magazinesReq != nil { + magazinesRes = &res.MagazinesResponse{ + ID: magazinesReq.ID, + Title: magazinesReq.Title, + Description: magazinesReq.Description, + ThumbnailPath: magazinesReq.ThumbnailPath, + PageUrl: magazinesReq.PageUrl, + CreatedById: magazinesReq.CreatedById, + StatusId: magazinesReq.StatusId, + IsPublish: magazinesReq.IsPublish, + PublishedAt: magazinesReq.PublishedAt, + IsActive: magazinesReq.IsActive, + CreatedAt: magazinesReq.CreatedAt, + UpdatedAt: magazinesReq.UpdatedAt, + MagazineFiles: magazineFilesArr, + } + + if magazinesReq.ThumbnailPath != nil { + magazinesRes.ThumbnailUrl = host + "/magazines/thumbnail/viewer/" + *magazinesReq.ThumbnailName + } + } + return magazinesRes +} diff --git a/app/module/magazines/repository/magazines.repository.go b/app/module/magazines/repository/magazines.repository.go new file mode 100644 index 0000000..08ad229 --- /dev/null +++ b/app/module/magazines/repository/magazines.repository.go @@ -0,0 +1,118 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/magazines/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" +) + +type magazinesRepository struct { + DB *database.Database +} + +// MagazinesRepository define interface of IMagazinesRepository +type MagazinesRepository interface { + GetAll(req request.MagazinesQueryRequest) (magaziness []*entity.Magazines, paging paginator.Pagination, err error) + FindOne(id uint) (magazines *entity.Magazines, err error) + FindByFilename(thumbnailName string) (magazineReturn *entity.Magazines, err error) + Create(magazines *entity.Magazines) (magazineReturn *entity.Magazines, err error) + Update(id uint, magazines *entity.Magazines) (err error) + Delete(id uint) (err error) +} + +func NewMagazinesRepository(db *database.Database) MagazinesRepository { + return &magazinesRepository{ + DB: db, + } +} + +// implement interface of IMagazinesRepository +func (_i *magazinesRepository) GetAll(req request.MagazinesQueryRequest) (magaziness []*entity.Magazines, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Magazines{}) + + 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.CreatedById != nil { + query = query.Where("created_by_id = ?", req.CreatedById) + } + if req.IsPublish != nil { + query = query.Where("is_publish = ?", req.IsPublish) + } + if req.StatusId != nil { + query = query.Where("status_id = ?", req.StatusId) + } + query.Count(&count) + + if req.Pagination.SortBy != "" { + direction := "ASC" + if req.Pagination.Sort == "desc" { + direction = "DESC" + } + query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction)) + } + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&magaziness).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *magazinesRepository) FindOne(id uint) (magazines *entity.Magazines, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.First(&magazines).Error; err != nil { + return nil, err + } + + return magazines, nil +} + +func (_i *magazinesRepository) FindByFilename(thumbnailName string) (magazines *entity.Magazines, err error) { + query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName) + + if err := query.First(&magazines).Error; err != nil { + return nil, err + } + + return magazines, nil +} + +func (_i *magazinesRepository) Create(magazines *entity.Magazines) (magazineReturn *entity.Magazines, err error) { + result := _i.DB.DB.Create(magazines) + return magazines, result.Error +} + +func (_i *magazinesRepository) Update(id uint, magazines *entity.Magazines) (err error) { + magazinesMap, err := utilSvc.StructToMap(magazines) + if err != nil { + return err + } + query := _i.DB.DB.Model(&entity.Magazines{}).Where(&entity.Magazines{ID: id}) + return query.Updates(magazinesMap).Error +} + +func (_i *magazinesRepository) Delete(id uint) error { + query := _i.DB.DB.Model(&entity.Magazines{}).Where("id = ?", id) + return query.Delete(&entity.Magazines{}).Error +} diff --git a/app/module/magazines/request/magazines.request.go b/app/module/magazines/request/magazines.request.go new file mode 100644 index 0000000..ec00590 --- /dev/null +++ b/app/module/magazines/request/magazines.request.go @@ -0,0 +1,119 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type MagazinesGeneric interface { + ToEntity() +} + +type MagazinesQueryRequest struct { + Title *string `json:"title"` + Description *string `json:"description"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl *string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *int `json:"createdById"` + StatusId *int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MagazinesCreateRequest struct { + Title string `json:"title" validate:"required"` + Description string `json:"description" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl *string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazinesCreateRequest) ToEntity() *entity.Magazines { + return &entity.Magazines{ + Title: req.Title, + Description: req.Description, + ThumbnailPath: req.ThumbnailPath, + ThumbnailUrl: req.ThumbnailUrl, + PageUrl: req.PageUrl, + CreatedById: req.CreatedById, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + IsActive: true, + } +} + +type MagazinesUpdateRequest 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"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl *string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` +} + +func (req MagazinesUpdateRequest) ToEntity() *entity.Magazines { + return &entity.Magazines{ + ID: req.ID, + Title: req.Title, + Description: req.Description, + ThumbnailPath: req.ThumbnailPath, + ThumbnailUrl: req.ThumbnailUrl, + PageUrl: req.PageUrl, + CreatedById: req.CreatedById, + StatusId: req.StatusId, + IsPublish: req.IsPublish, + PublishedAt: req.PublishedAt, + UpdatedAt: time.Now(), + } +} + +type MagazinesQueryRequestContext struct { + Title string `json:"title"` + Description string `json:"description"` + CreatedById string `json:"createdById"` + IsPublish string `json:"isPublish"` + StatusId string `json:"statusId"` +} + +func (req MagazinesQueryRequestContext) ToParamRequest() MagazinesQueryRequest { + var request MagazinesQueryRequest + + if title := req.Title; title != "" { + request.Title = &title + } + if description := req.Description; description != "" { + request.Description = &description + } + 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 createdByIdStr := req.CreatedById; createdByIdStr != "" { + createdById, err := strconv.Atoi(createdByIdStr) + if err == nil { + request.CreatedById = &createdById + } + } + + return request +} diff --git a/app/module/magazines/response/magazines.response.go b/app/module/magazines/response/magazines.response.go new file mode 100644 index 0000000..2134919 --- /dev/null +++ b/app/module/magazines/response/magazines.response.go @@ -0,0 +1,24 @@ +package response + +import ( + magazineFilesResponse "campaign-pool-be/app/module/magazine_files/response" + "time" +) + +type MagazinesResponse struct { + ID uint `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + ThumbnailPath *string `json:"thumbnailPath"` + ThumbnailUrl string `json:"thumbnailUrl"` + PageUrl *string `json:"pageUrl"` + CreatedById *uint `json:"createdById"` + StatusId int `json:"statusId"` + IsPublish *bool `json:"isPublish"` + PublishedAt *time.Time `json:"publishedAt"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + MagazineFiles []*magazineFilesResponse.MagazineFilesResponse `json:"files"` +} diff --git a/app/module/magazines/service/magazines.service.go b/app/module/magazines/service/magazines.service.go new file mode 100644 index 0000000..5f19c3a --- /dev/null +++ b/app/module/magazines/service/magazines.service.go @@ -0,0 +1,257 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + magazineFilesRepository "campaign-pool-be/app/module/magazine_files/repository" + "campaign-pool-be/app/module/magazines/mapper" + "campaign-pool-be/app/module/magazines/repository" + "campaign-pool-be/app/module/magazines/request" + "campaign-pool-be/app/module/magazines/response" + usersRepository "campaign-pool-be/app/module/users/repository" + config "campaign-pool-be/config/config" + minioStorage "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "context" + "fmt" + "io" + "log" + "math/rand" + "mime" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/minio/minio-go/v7" + "github.com/rs/zerolog" +) + +// MagazinesService +type magazinesService struct { + Repo repository.MagazinesRepository + UsersRepo usersRepository.UsersRepository + MagazineFilesRepo magazineFilesRepository.MagazineFilesRepository + MinioStorage *minioStorage.MinioStorage + Log zerolog.Logger + Cfg *config.Config +} + +// MagazinesService define interface of IMagazinesService +type MagazinesService interface { + All(req request.MagazinesQueryRequest) (magazines []*response.MagazinesResponse, paging paginator.Pagination, err error) + Show(id uint) (magazines *response.MagazinesResponse, err error) + Save(req request.MagazinesCreateRequest, authToken string) (magazines *entity.Magazines, err error) + Update(id uint, req request.MagazinesUpdateRequest) (err error) + SaveThumbnail(c *fiber.Ctx) (err error) + Viewer(c *fiber.Ctx) (err error) + Delete(id uint) error +} + +// NewMagazinesService init MagazinesService +func NewMagazinesService(repo repository.MagazinesRepository, magazineFilesRepo magazineFilesRepository.MagazineFilesRepository, usersRepo usersRepository.UsersRepository, minioStorage *minioStorage.MinioStorage, log zerolog.Logger, cfg *config.Config) MagazinesService { + + return &magazinesService{ + Repo: repo, + MagazineFilesRepo: magazineFilesRepo, + UsersRepo: usersRepo, + MinioStorage: minioStorage, + Log: log, + Cfg: cfg, + } +} + +// All implement interface of MagazinesService +func (_i *magazinesService) All(req request.MagazinesQueryRequest) (magaziness []*response.MagazinesResponse, 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 { + magaziness = append(magaziness, mapper.MagazinesResponseMapper(result, _i.MagazineFilesRepo, host)) + } + + return +} + +func (_i *magazinesService) Show(id uint) (magazines *response.MagazinesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + host := _i.Cfg.App.Domain + return mapper.MagazinesResponseMapper(result, _i.MagazineFilesRepo, host), nil +} + +func (_i *magazinesService) Save(req request.MagazinesCreateRequest, authToken string) (magazines *entity.Magazines, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + + saveMagazineResponse, err := _i.Repo.Create(newReq) + if err != nil { + return nil, err + } + + return saveMagazineResponse, nil +} + +func (_i *magazinesService) SaveThumbnail(c *fiber.Ctx) (err error) { + + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:magazinesService", "Methods:SaveThumbnail"). + Interface("id", id).Msg("") + + 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) + filename = strings.ReplaceAll(filename, " ", "") + filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))]) + extension := filepath.Ext(file.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("magazines/thumbnail/%d/%d/%s", now.Year(), now.Month(), newFilename) + + findCategory, err := _i.Repo.FindOne(uint(id)) + findCategory.ThumbnailName = &newFilename + 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 *magazinesService) Update(id uint, req request.MagazinesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + entity := req.ToEntity() + + return _i.Repo.Update(id, entity) +} + +func (_i *magazinesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} + +func (_i *magazinesService) Viewer(c *fiber.Ctx) (err error) { + thumbnailName := c.Params("thumbnailName") + + emptyImage := "empty-image.jpg" + searchThumbnail := emptyImage + if thumbnailName != emptyImage { + result, err := _i.Repo.FindByFilename(thumbnailName) + if err != nil { + return err + } + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "magazinesService:Viewer"). + Interface("resultThumbnail", result.ThumbnailPath).Msg("") + + if result.ThumbnailPath != nil { + searchThumbnail = *result.ThumbnailPath + } else { + searchThumbnail = "magazines/thumbnail/" + emptyImage + } + } else { + searchThumbnail = "magazines/thumbnail/" + emptyImage + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := searchThumbnail + + // 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] +} diff --git a/app/module/master_menus/controller/controller.go b/app/module/master_menus/controller/controller.go new file mode 100644 index 0000000..7df327e --- /dev/null +++ b/app/module/master_menus/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/master_menus/service" + +type Controller struct { + MasterMenus MasterMenusController +} + +func NewController(MasterMenusService service.MasterMenusService) *Controller { + return &Controller{ + MasterMenus: NewMasterMenusController(MasterMenusService), + } +} diff --git a/app/module/master_menus/controller/master_menus.controller.go b/app/module/master_menus/controller/master_menus.controller.go new file mode 100644 index 0000000..49758ae --- /dev/null +++ b/app/module/master_menus/controller/master_menus.controller.go @@ -0,0 +1,195 @@ +package controller + +import ( + "campaign-pool-be/app/module/master_menus/request" + "campaign-pool-be/app/module/master_menus/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type masterMenusController struct { + masterMenusService service.MasterMenusService +} + +type MasterMenusController 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 NewMasterMenusController(masterMenusService service.MasterMenusService) MasterMenusController { + return &masterMenusController{ + masterMenusService: masterMenusService, + } +} + +// All MasterMenus +// @Summary Get all MasterMenus +// @Description API for getting all MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param req query request.MasterMenusQueryRequest 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 /master-menus [get] +func (_i *masterMenusController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MasterMenusQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + ParentMenuId: c.Query("parentMenuId"), + ModuleId: c.Query("moduleId"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + masterMenusData, paging, err := _i.masterMenusService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus list successfully retrieved"}, + Data: masterMenusData, + Meta: paging, + }) +} + +// Show get one MasterMenus +// @Summary Get one MasterMenus +// @Description API for getting one MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param id path int true "MasterMenus ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus/{id} [get] +func (_i *masterMenusController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + masterMenusData, err := _i.masterMenusService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully retrieved"}, + Data: masterMenusData, + }) +} + +// Save create MasterMenus +// @Summary Create MasterMenus +// @Description API for create MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.MasterMenusCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus [post] +func (_i *masterMenusController) Save(c *fiber.Ctx) error { + req := new(request.MasterMenusCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.masterMenusService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully created"}, + }) +} + +// Update MasterMenus +// @Summary Update MasterMenus +// @Description API for update MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Body request.MasterMenusUpdateRequest +// @Param id path int true "MasterMenus 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 /master-menus/{id} [put] +func (_i *masterMenusController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MasterMenusUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.masterMenusService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully updated"}, + }) +} + +// Delete MasterMenus +// @Summary Delete MasterMenus +// @Description API for delete MasterMenus +// @Tags MasterMenus +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "MasterMenus ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-menus/{id} [delete] +func (_i *masterMenusController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.masterMenusService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterMenus successfully deleted"}, + }) +} diff --git a/app/module/master_menus/mapper/master_menus.mapper.go b/app/module/master_menus/mapper/master_menus.mapper.go new file mode 100644 index 0000000..fc21b5e --- /dev/null +++ b/app/module/master_menus/mapper/master_menus.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/master_menus/response" +) + +func MasterMenusResponseMapper(masterMenusReq *entity.MasterMenus) (masterMenusRes *res.MasterMenusResponse) { + if masterMenusReq != nil { + masterMenusRes = &res.MasterMenusResponse{ + ID: masterMenusReq.ID, + Name: masterMenusReq.Name, + Description: masterMenusReq.Description, + ModuleId: masterMenusReq.ModuleId, + ParentMenuId: masterMenusReq.ParentMenuId, + Icon: masterMenusReq.Icon, + Position: masterMenusReq.Position, + StatusId: masterMenusReq.StatusId, + IsActive: masterMenusReq.IsActive, + CreatedAt: masterMenusReq.CreatedAt, + UpdatedAt: masterMenusReq.UpdatedAt, + } + } + return masterMenusRes +} diff --git a/app/module/master_menus/master_menus.module.go b/app/module/master_menus/master_menus.module.go new file mode 100644 index 0000000..6be27ef --- /dev/null +++ b/app/module/master_menus/master_menus.module.go @@ -0,0 +1,54 @@ +package master_menus + +import ( + "campaign-pool-be/app/module/master_menus/controller" + "campaign-pool-be/app/module/master_menus/repository" + "campaign-pool-be/app/module/master_menus/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of MasterMenusRouter +type MasterMenusRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MasterMenus module +var NewMasterMenusModule = fx.Options( + // register repository of MasterMenus module + fx.Provide(repository.NewMasterMenusRepository), + + // register service of MasterMenus module + fx.Provide(service.NewMasterMenusService), + + // register controller of MasterMenus module + fx.Provide(controller.NewController), + + // register router of MasterMenus module + fx.Provide(NewMasterMenusRouter), +) + +// init MasterMenusRouter +func NewMasterMenusRouter(fiber *fiber.App, controller *controller.Controller) *MasterMenusRouter { + return &MasterMenusRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MasterMenus module +func (_i *MasterMenusRouter) RegisterMasterMenusRoutes() { + // define controllers + masterMenusController := _i.Controller.MasterMenus + + // define routes + _i.App.Route("/master-menus", func(router fiber.Router) { + router.Get("/", masterMenusController.All) + router.Get("/:id", masterMenusController.Show) + router.Post("/", masterMenusController.Save) + router.Put("/:id", masterMenusController.Update) + router.Delete("/:id", masterMenusController.Delete) + }) +} diff --git a/app/module/master_menus/repository/master_menus.repository.go b/app/module/master_menus/repository/master_menus.repository.go new file mode 100644 index 0000000..1ef84b8 --- /dev/null +++ b/app/module/master_menus/repository/master_menus.repository.go @@ -0,0 +1,108 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/master_menus/request" + "campaign-pool-be/utils/paginator" + "fmt" + "strings" +) + +type masterMenusRepository struct { + DB *database.Database +} + +// MasterMenusRepository define interface of IMasterMenusRepository +type MasterMenusRepository interface { + GetAll(req request.MasterMenusQueryRequest) (masterMenuss []*entity.MasterMenus, paging paginator.Pagination, err error) + FindOne(id uint) (masterMenus *entity.MasterMenus, err error) + FindLastMenuPosition() (position *int, err error) + Create(masterMenus *entity.MasterMenus) (err error) + Update(id uint, masterMenus *entity.MasterMenus) (err error) + Delete(id uint) (err error) +} + +func NewMasterMenusRepository(db *database.Database) MasterMenusRepository { + return &masterMenusRepository{ + DB: db, + } +} + +// implement interface of IMasterMenusRepository +func (_i *masterMenusRepository) GetAll(req request.MasterMenusQueryRequest) (masterMenuss []*entity.MasterMenus, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MasterMenus{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + if req.ModuleId != nil { + query = query.Where("module_id = ?", req.ModuleId) + } + if req.ParentMenuId != nil { + query = query.Where("parent_menu_id = ?", req.ParentMenuId) + } + 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(&masterMenuss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *masterMenusRepository) FindOne(id uint) (masterMenus *entity.MasterMenus, err error) { + if err := _i.DB.DB.First(&masterMenus, id).Error; err != nil { + return nil, err + } + + return masterMenus, nil +} + +func (_i *masterMenusRepository) FindLastMenuPosition() (position *int, err error) { + var masterMenus *entity.MasterMenus + if err := _i.DB.DB.Where("position IS NOT NULL").Order(fmt.Sprintf("%s %s", "position", "DESC")).First(&masterMenus).Error; err != nil { + return nil, err + } + + return masterMenus.Position, nil +} + +func (_i *masterMenusRepository) Create(masterMenus *entity.MasterMenus) (err error) { + return _i.DB.DB.Create(masterMenus).Error +} + +func (_i *masterMenusRepository) Update(id uint, masterMenus *entity.MasterMenus) (err error) { + return _i.DB.DB.Model(&entity.MasterMenus{}). + Where(&entity.MasterMenus{ID: id}). + Updates(masterMenus).Error +} + +func (_i *masterMenusRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MasterMenus{}, id).Error +} diff --git a/app/module/master_menus/request/master_menus.request.go b/app/module/master_menus/request/master_menus.request.go new file mode 100644 index 0000000..de14cfb --- /dev/null +++ b/app/module/master_menus/request/master_menus.request.go @@ -0,0 +1,108 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type MasterMenusGeneric interface { + ToEntity() +} + +type MasterMenusQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + ModuleId *int `json:"moduleId"` + ParentMenuId *int `json:"parentMenuId"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MasterMenusCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + ModuleId int `json:"moduleId" validate:"required"` + Group string `json:"group" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ParentMenuId *int `json:"parentMenuId"` + Icon *string `json:"icon"` +} + +func (req MasterMenusCreateRequest) ToEntity() *entity.MasterMenus { + return &entity.MasterMenus{ + Name: req.Name, + Description: req.Description, + ModuleId: req.ModuleId, + ParentMenuId: req.ParentMenuId, + Icon: req.Icon, + Group: req.Group, + StatusId: req.StatusId, + } +} + +type MasterMenusUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + ModuleId int `json:"moduleId" validate:"required"` + Group string `json:"group" validate:"required"` + StatusId int `json:"statusId" validate:"required"` + ParentMenuId *int `json:"parentMenuId"` + Icon *string `json:"icon"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (req MasterMenusUpdateRequest) ToEntity() *entity.MasterMenus { + return &entity.MasterMenus{ + ID: req.ID, + Name: req.Name, + Description: req.Description, + ModuleId: req.ModuleId, + ParentMenuId: req.ParentMenuId, + Icon: req.Icon, + Group: req.Group, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type MasterMenusQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + ModuleId string `json:"moduleId"` + ParentMenuId string `json:"parentMenuId"` + StatusId string `json:"statusId"` +} + +func (req MasterMenusQueryRequestContext) ToParamRequest() MasterMenusQueryRequest { + var request MasterMenusQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if description := req.Description; description != "" { + request.Description = &description + } + if moduleIdStr := req.ModuleId; moduleIdStr != "" { + moduleId, err := strconv.Atoi(moduleIdStr) + if err == nil { + request.ModuleId = &moduleId + } + } + if parentMenuIdStr := req.ParentMenuId; parentMenuIdStr != "" { + parentMenuId, err := strconv.Atoi(parentMenuIdStr) + if err == nil { + request.ParentMenuId = &parentMenuId + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/master_menus/response/master_menus.response.go b/app/module/master_menus/response/master_menus.response.go new file mode 100644 index 0000000..9ff070c --- /dev/null +++ b/app/module/master_menus/response/master_menus.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type MasterMenusResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ModuleId int `json:"moduleId"` + ParentMenuId *int `json:"parentMenuId"` + Icon *string `json:"icon"` + Position *int `json:"position"` + StatusId int `json:"statusId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/master_menus/service/master_menus.service.go b/app/module/master_menus/service/master_menus.service.go new file mode 100644 index 0000000..0d58fa7 --- /dev/null +++ b/app/module/master_menus/service/master_menus.service.go @@ -0,0 +1,91 @@ +package service + +import ( + "campaign-pool-be/app/module/master_menus/mapper" + "campaign-pool-be/app/module/master_menus/repository" + "campaign-pool-be/app/module/master_menus/request" + "campaign-pool-be/app/module/master_menus/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// MasterMenusService +type masterMenusService struct { + Repo repository.MasterMenusRepository + Log zerolog.Logger +} + +// MasterMenusService define interface of IMasterMenusService +type MasterMenusService interface { + All(req request.MasterMenusQueryRequest) (masterMenus []*response.MasterMenusResponse, paging paginator.Pagination, err error) + Show(id uint) (masterMenus *response.MasterMenusResponse, err error) + Save(req request.MasterMenusCreateRequest) (err error) + Update(id uint, req request.MasterMenusUpdateRequest) (err error) + Delete(id uint) error +} + +// NewMasterMenusService init MasterMenusService +func NewMasterMenusService(repo repository.MasterMenusRepository, log zerolog.Logger) MasterMenusService { + + return &masterMenusService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of MasterMenusService +func (_i *masterMenusService) All(req request.MasterMenusQueryRequest) (masterMenuss []*response.MasterMenusResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + masterMenuss = append(masterMenuss, mapper.MasterMenusResponseMapper(result)) + } + + return +} + +func (_i *masterMenusService) Show(id uint) (masterMenus *response.MasterMenusResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.MasterMenusResponseMapper(result), nil +} + +func (_i *masterMenusService) Save(req request.MasterMenusCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + newReq := req.ToEntity() + var latestPosition, _ = _i.Repo.FindLastMenuPosition() + if err != nil { + return err + } + *latestPosition = *latestPosition + 1 + + if latestPosition != nil { + newReq.Position = latestPosition + } + + return _i.Repo.Create(newReq) +} + +func (_i *masterMenusService) Update(id uint, req request.MasterMenusUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *masterMenusService) 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) +} diff --git a/app/module/master_modules/controller/controller.go b/app/module/master_modules/controller/controller.go new file mode 100644 index 0000000..37895ba --- /dev/null +++ b/app/module/master_modules/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/master_modules/service" + +type Controller struct { + MasterModules MasterModulesController +} + +func NewController(MasterModulesService service.MasterModulesService) *Controller { + return &Controller{ + MasterModules: NewMasterModulesController(MasterModulesService), + } +} diff --git a/app/module/master_modules/controller/master_modules.controller.go b/app/module/master_modules/controller/master_modules.controller.go new file mode 100644 index 0000000..7992731 --- /dev/null +++ b/app/module/master_modules/controller/master_modules.controller.go @@ -0,0 +1,192 @@ +package controller + +import ( + "campaign-pool-be/app/module/master_modules/request" + "campaign-pool-be/app/module/master_modules/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type masterModulesController struct { + masterModulesService service.MasterModulesService +} + +type MasterModulesController 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 NewMasterModulesController(masterModulesService service.MasterModulesService) MasterModulesController { + return &masterModulesController{ + masterModulesService: masterModulesService, + } +} + +// All MasterModules +// @Summary Get all MasterModules +// @Description API for getting all MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param req query request.MasterModulesQueryRequest 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 /master-modules [get] +func (_i *masterModulesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.MasterModulesQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + masterModulesData, paging, err := _i.masterModulesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules list successfully retrieved"}, + Data: masterModulesData, + Meta: paging, + }) +} + +// Show MasterModules +// @Summary Get one MasterModules +// @Description API for getting one MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param id path int true "MasterModules ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules/{id} [get] +func (_i *masterModulesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + masterModulesData, err := _i.masterModulesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully retrieved"}, + Data: masterModulesData, + }) +} + +// Save MasterModules +// @Summary Create MasterModules +// @Description API for create MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.MasterModulesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules [post] +func (_i *masterModulesController) Save(c *fiber.Ctx) error { + req := new(request.MasterModulesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.masterModulesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully created"}, + }) +} + +// Update MasterModules +// @Summary Update MasterModules +// @Description API for update MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "MasterModules ID" +// @Param payload body request.MasterModulesUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules/{id} [put] +func (_i *masterModulesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MasterModulesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.masterModulesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully updated"}, + }) +} + +// Delete MasterModules +// @Summary Delete MasterModules +// @Description API for delete MasterModules +// @Tags MasterModules +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "MasterModules ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /master-modules/{id} [delete] +func (_i *masterModulesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.masterModulesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"MasterModules successfully deleted"}, + }) +} diff --git a/app/module/master_modules/mapper/master_modules.mapper.go b/app/module/master_modules/mapper/master_modules.mapper.go new file mode 100644 index 0000000..085a9e3 --- /dev/null +++ b/app/module/master_modules/mapper/master_modules.mapper.go @@ -0,0 +1,22 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/master_modules/response" +) + +func MasterModulesResponseMapper(masterModulesReq *entity.MasterModules) (masterModulesRes *res.MasterModulesResponse) { + if masterModulesReq != nil { + masterModulesRes = &res.MasterModulesResponse{ + ID: masterModulesReq.ID, + Name: masterModulesReq.Name, + Description: masterModulesReq.Description, + PathUrl: masterModulesReq.PathUrl, + StatusId: masterModulesReq.StatusId, + IsActive: masterModulesReq.IsActive, + CreatedAt: masterModulesReq.CreatedAt, + UpdatedAt: masterModulesReq.UpdatedAt, + } + } + return masterModulesRes +} diff --git a/app/module/master_modules/master_modules.module.go b/app/module/master_modules/master_modules.module.go new file mode 100644 index 0000000..893b754 --- /dev/null +++ b/app/module/master_modules/master_modules.module.go @@ -0,0 +1,54 @@ +package master_modules + +import ( + "campaign-pool-be/app/module/master_modules/controller" + "campaign-pool-be/app/module/master_modules/repository" + "campaign-pool-be/app/module/master_modules/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of MasterModulesRouter +type MasterModulesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MasterModules module +var NewMasterModulesModule = fx.Options( + // register repository of MasterModules module + fx.Provide(repository.NewMasterModulesRepository), + + // register service of MasterModules module + fx.Provide(service.NewMasterModulesService), + + // register controller of MasterModules module + fx.Provide(controller.NewController), + + // register router of MasterModules module + fx.Provide(NewMasterModulesRouter), +) + +// init MasterModulesRouter +func NewMasterModulesRouter(fiber *fiber.App, controller *controller.Controller) *MasterModulesRouter { + return &MasterModulesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MasterModules module +func (_i *MasterModulesRouter) RegisterMasterModulesRoutes() { + // define controllers + masterModulesController := _i.Controller.MasterModules + + // define routes + _i.App.Route("/master-modules", func(router fiber.Router) { + router.Get("/", masterModulesController.All) + router.Get("/:id", masterModulesController.Show) + router.Post("/", masterModulesController.Save) + router.Put("/:id", masterModulesController.Update) + router.Delete("/:id", masterModulesController.Delete) + }) +} diff --git a/app/module/master_modules/repository/master_modules.repository.go b/app/module/master_modules/repository/master_modules.repository.go new file mode 100644 index 0000000..87b0549 --- /dev/null +++ b/app/module/master_modules/repository/master_modules.repository.go @@ -0,0 +1,92 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/master_modules/request" + "campaign-pool-be/utils/paginator" + "fmt" + "strings" +) + +type masterModulesRepository struct { + DB *database.Database +} + +// MasterModulesRepository define interface of IMasterModulesRepository +type MasterModulesRepository interface { + GetAll(req request.MasterModulesQueryRequest) (masterModuless []*entity.MasterModules, paging paginator.Pagination, err error) + FindOne(id uint) (masterModules *entity.MasterModules, err error) + Create(masterModules *entity.MasterModules) (err error) + Update(id uint, masterModules *entity.MasterModules) (err error) + Delete(id uint) (err error) +} + +func NewMasterModulesRepository(db *database.Database) MasterModulesRepository { + return &masterModulesRepository{ + DB: db, + } +} + +// implement interface of IMasterModulesRepository +func (_i *masterModulesRepository) GetAll(req request.MasterModulesQueryRequest) (masterModuless []*entity.MasterModules, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MasterModules{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Description != nil && *req.Description != "" { + description := strings.ToLower(*req.Description) + query = query.Where("LOWER(description) LIKE ?", "%"+strings.ToLower(description)+"%") + } + 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(&masterModuless).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *masterModulesRepository) FindOne(id uint) (masterModules *entity.MasterModules, err error) { + if err := _i.DB.DB.First(&masterModules, id).Error; err != nil { + return nil, err + } + + return masterModules, nil +} + +func (_i *masterModulesRepository) Create(masterModules *entity.MasterModules) (err error) { + return _i.DB.DB.Create(masterModules).Error +} + +func (_i *masterModulesRepository) Update(id uint, masterModules *entity.MasterModules) (err error) { + return _i.DB.DB.Model(&entity.MasterModules{}). + Where(&entity.MasterModules{ID: id}). + Updates(masterModules).Error +} + +func (_i *masterModulesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MasterModules{}, id).Error +} diff --git a/app/module/master_modules/request/master_modules.request.go b/app/module/master_modules/request/master_modules.request.go new file mode 100644 index 0000000..38594fd --- /dev/null +++ b/app/module/master_modules/request/master_modules.request.go @@ -0,0 +1,79 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type MasterModulesGeneric interface { + ToEntity() +} + +type MasterModulesQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MasterModulesCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + PathUrl string `json:"pathUrl" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req MasterModulesCreateRequest) ToEntity() *entity.MasterModules { + return &entity.MasterModules{ + Name: req.Name, + Description: req.Description, + PathUrl: req.PathUrl, + StatusId: req.StatusId, + } +} + +type MasterModulesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + PathUrl string `json:"pathUrl" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req MasterModulesUpdateRequest) ToEntity() *entity.MasterModules { + return &entity.MasterModules{ + ID: req.ID, + Name: req.Name, + Description: req.Description, + PathUrl: req.PathUrl, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type MasterModulesQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + StatusId string `json:"statusId"` +} + +func (req MasterModulesQueryRequestContext) ToParamRequest() MasterModulesQueryRequest { + var request MasterModulesQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if description := req.Description; description != "" { + request.Description = &description + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/master_modules/response/master_modules.response.go b/app/module/master_modules/response/master_modules.response.go new file mode 100644 index 0000000..edfa5b2 --- /dev/null +++ b/app/module/master_modules/response/master_modules.response.go @@ -0,0 +1,14 @@ +package response + +import "time" + +type MasterModulesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + PathUrl string `json:"pathUrl"` + StatusId int `json:"statusId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/master_modules/service/master_modules.service.go b/app/module/master_modules/service/master_modules.service.go new file mode 100644 index 0000000..5fb4dea --- /dev/null +++ b/app/module/master_modules/service/master_modules.service.go @@ -0,0 +1,80 @@ +package service + +import ( + "campaign-pool-be/app/module/master_modules/mapper" + "campaign-pool-be/app/module/master_modules/repository" + "campaign-pool-be/app/module/master_modules/request" + "campaign-pool-be/app/module/master_modules/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// MasterModulesService +type masterModulesService struct { + Repo repository.MasterModulesRepository + Log zerolog.Logger +} + +// MasterModulesService define interface of IMasterModulesService +type MasterModulesService interface { + All(req request.MasterModulesQueryRequest) (masterModules []*response.MasterModulesResponse, paging paginator.Pagination, err error) + Show(id uint) (masterModules *response.MasterModulesResponse, err error) + Save(req request.MasterModulesCreateRequest) (err error) + Update(id uint, req request.MasterModulesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewMasterModulesService init MasterModulesService +func NewMasterModulesService(repo repository.MasterModulesRepository, log zerolog.Logger) MasterModulesService { + + return &masterModulesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of MasterModulesService +func (_i *masterModulesService) All(req request.MasterModulesQueryRequest) (masterModuless []*response.MasterModulesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + masterModuless = append(masterModuless, mapper.MasterModulesResponseMapper(result)) + } + + return +} + +func (_i *masterModulesService) Show(id uint) (masterModules *response.MasterModulesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.MasterModulesResponseMapper(result), nil +} + +func (_i *masterModulesService) Save(req request.MasterModulesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *masterModulesService) Update(id uint, req request.MasterModulesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *masterModulesService) 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) +} diff --git a/app/module/master_statuses/controller/controller.go b/app/module/master_statuses/controller/controller.go new file mode 100644 index 0000000..43017f0 --- /dev/null +++ b/app/module/master_statuses/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/master_statuses/service" + +type Controller struct { + MasterStatuses MasterStatusesController +} + +func NewController(MasterStatusesService service.MasterStatusesService) *Controller { + return &Controller{ + MasterStatuses: NewMasterStatusesController(MasterStatusesService), + } +} diff --git a/app/module/master_statuses/controller/master_statuses.controller.go b/app/module/master_statuses/controller/master_statuses.controller.go new file mode 100644 index 0000000..d078483 --- /dev/null +++ b/app/module/master_statuses/controller/master_statuses.controller.go @@ -0,0 +1,182 @@ +package controller + +import ( + "campaign-pool-be/app/module/master_statuses/request" + "campaign-pool-be/app/module/master_statuses/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type masterStatusesController struct { + masterStatusesService service.MasterStatusesService +} + +type MasterStatusesController 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 NewMasterStatusesController(masterStatusesService service.MasterStatusesService) MasterStatusesController { + return &masterStatusesController{ + masterStatusesService: masterStatusesService, + } +} + +// All MasterStatuses +// @Summary Get all MasterStatuses +// @Description API for getting all MasterStatuses +// @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 /master-statuses [get] +func (_i *masterStatusesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.MasterStatusesQueryRequest + req.Pagination = paginate + + masterStatusesData, paging, err := _i.masterStatusesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses list successfully retrieved"}, + Data: masterStatusesData, + Meta: paging, + }) +} + +// Show MasterStatuses +// @Summary Get one MasterStatuses +// @Description API for getting one MasterStatuses +// @Tags Untags +// @Security Bearer +// @Param id path int true "MasterStatuses 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 /master-statuses/{id} [get] +func (_i *masterStatusesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + masterStatusesData, err := _i.masterStatusesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully retrieved"}, + Data: masterStatusesData, + }) +} + +// Save MasterStatuses +// @Summary Create MasterStatuses +// @Description API for create MasterStatuses +// @Tags Untags +// @Security Bearer +// @Body request.MasterStatusesCreateRequest +// @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 /master-statuses [post] +func (_i *masterStatusesController) Save(c *fiber.Ctx) error { + req := new(request.MasterStatusesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.masterStatusesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully created"}, + }) +} + +// Update MasterStatuses +// @Summary Update MasterStatuses +// @Description API for update MasterStatuses +// @Tags Untags +// @Security Bearer +// @Body request.MasterStatusesUpdateRequest +// @Param id path int true "MasterStatuses 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 /master-statuses/{id} [put] +func (_i *masterStatusesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.MasterStatusesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.masterStatusesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully updated"}, + }) +} + +// Delete MasterStatuses +// @Summary Delete MasterStatuses +// @Description API for delete MasterStatuses +// @Tags Untags +// @Security Bearer +// @Param id path int true "MasterStatuses 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 /master-statuses/{id} [delete] +func (_i *masterStatusesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.masterStatusesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"MasterStatuses successfully deleted"}, + }) +} diff --git a/app/module/master_statuses/mapper/master_statuses.mapper.go b/app/module/master_statuses/mapper/master_statuses.mapper.go new file mode 100644 index 0000000..db91606 --- /dev/null +++ b/app/module/master_statuses/mapper/master_statuses.mapper.go @@ -0,0 +1,17 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/master_statuses/response" +) + +func MasterStatusesResponseMapper(masterStatusesReq *entity.MasterStatuses) (masterStatusesRes *res.MasterStatusesResponse) { + if masterStatusesReq != nil { + masterStatusesRes = &res.MasterStatusesResponse{ + ID: masterStatusesReq.ID, + Name: masterStatusesReq.Name, + IsActive: masterStatusesReq.IsActive, + } + } + return masterStatusesRes +} diff --git a/app/module/master_statuses/master_statuses.module.go b/app/module/master_statuses/master_statuses.module.go new file mode 100644 index 0000000..c77da3f --- /dev/null +++ b/app/module/master_statuses/master_statuses.module.go @@ -0,0 +1,54 @@ +package master_statuses + +import ( + "campaign-pool-be/app/module/master_statuses/controller" + "campaign-pool-be/app/module/master_statuses/repository" + "campaign-pool-be/app/module/master_statuses/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of MasterStatusesRouter +type MasterStatusesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of MasterStatuses module +var NewMasterStatusesModule = fx.Options( + // register repository of MasterStatuses module + fx.Provide(repository.NewMasterStatusesRepository), + + // register service of MasterStatuses module + fx.Provide(service.NewMasterStatusesService), + + // register controller of MasterStatuses module + fx.Provide(controller.NewController), + + // register router of MasterStatuses module + fx.Provide(NewMasterStatusesRouter), +) + +// init MasterStatusesRouter +func NewMasterStatusesRouter(fiber *fiber.App, controller *controller.Controller) *MasterStatusesRouter { + return &MasterStatusesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of MasterStatuses module +func (_i *MasterStatusesRouter) RegisterMasterStatusesRoutes() { + // define controllers + masterStatusesController := _i.Controller.MasterStatuses + + // define routes + _i.App.Route("/master-statuses", func(router fiber.Router) { + router.Get("/", masterStatusesController.All) + router.Get("/:id", masterStatusesController.Show) + router.Post("/", masterStatusesController.Save) + router.Put("/:id", masterStatusesController.Update) + router.Delete("/:id", masterStatusesController.Delete) + }) +} diff --git a/app/module/master_statuses/repository/master_statuses.repository.go b/app/module/master_statuses/repository/master_statuses.repository.go new file mode 100644 index 0000000..2623776 --- /dev/null +++ b/app/module/master_statuses/repository/master_statuses.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/master_statuses/request" + "campaign-pool-be/utils/paginator" +) + +type masterStatusesRepository struct { + DB *database.Database +} + +// MasterStatusesRepository define interface of IMasterStatusesRepository +type MasterStatusesRepository interface { + GetAll(req request.MasterStatusesQueryRequest) (masterStatusess []*entity.MasterStatuses, paging paginator.Pagination, err error) + FindOne(id uint) (masterStatuses *entity.MasterStatuses, err error) + Create(masterStatuses *entity.MasterStatuses) (err error) + Update(id uint, masterStatuses *entity.MasterStatuses) (err error) + Delete(id uint) (err error) +} + +func NewMasterStatusesRepository(db *database.Database) MasterStatusesRepository { + return &masterStatusesRepository{ + DB: db, + } +} + +// implement interface of IMasterStatusesRepository +func (_i *masterStatusesRepository) GetAll(req request.MasterStatusesQueryRequest) (masterStatusess []*entity.MasterStatuses, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.MasterStatuses{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&masterStatusess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *masterStatusesRepository) FindOne(id uint) (masterStatuses *entity.MasterStatuses, err error) { + if err := _i.DB.DB.First(&masterStatuses, id).Error; err != nil { + return nil, err + } + + return masterStatuses, nil +} + +func (_i *masterStatusesRepository) Create(masterStatuses *entity.MasterStatuses) (err error) { + return _i.DB.DB.Create(masterStatuses).Error +} + +func (_i *masterStatusesRepository) Update(id uint, masterStatuses *entity.MasterStatuses) (err error) { + return _i.DB.DB.Model(&entity.MasterStatuses{}). + Where(&entity.MasterStatuses{ID: id}). + Updates(masterStatuses).Error +} + +func (_i *masterStatusesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.MasterStatuses{}, id).Error +} diff --git a/app/module/master_statuses/request/master_statuses.request.go b/app/module/master_statuses/request/master_statuses.request.go new file mode 100644 index 0000000..6959c26 --- /dev/null +++ b/app/module/master_statuses/request/master_statuses.request.go @@ -0,0 +1,42 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" +) + +type MasterStatusesGeneric interface { + ToEntity() +} + +type MasterStatusesQueryRequest struct { + Name string `json:"name" validate:"required"` + IsActive bool `json:"isActive" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type MasterStatusesCreateRequest struct { + Name string `json:"name" validate:"required"` + IsActive bool `json:"isActive" validate:"required"` +} + +func (req MasterStatusesCreateRequest) ToEntity() *entity.MasterStatuses { + return &entity.MasterStatuses{ + Name: req.Name, + IsActive: req.IsActive, + } +} + +type MasterStatusesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + IsActive bool `json:"isActive" validate:"required"` +} + +func (req MasterStatusesUpdateRequest) ToEntity() *entity.MasterStatuses { + return &entity.MasterStatuses{ + ID: req.ID, + Name: req.Name, + IsActive: req.IsActive, + } +} diff --git a/app/module/master_statuses/response/master_statuses.response.go b/app/module/master_statuses/response/master_statuses.response.go new file mode 100644 index 0000000..5aa8f64 --- /dev/null +++ b/app/module/master_statuses/response/master_statuses.response.go @@ -0,0 +1,7 @@ +package response + +type MasterStatusesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + IsActive bool `json:"isActive"` +} diff --git a/app/module/master_statuses/service/master_statuses.service.go b/app/module/master_statuses/service/master_statuses.service.go new file mode 100644 index 0000000..ff7b0e2 --- /dev/null +++ b/app/module/master_statuses/service/master_statuses.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "campaign-pool-be/app/module/master_statuses/mapper" + "campaign-pool-be/app/module/master_statuses/repository" + "campaign-pool-be/app/module/master_statuses/request" + "campaign-pool-be/app/module/master_statuses/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// MasterStatusesService +type masterStatusesService struct { + Repo repository.MasterStatusesRepository + Log zerolog.Logger +} + +// MasterStatusesService define interface of IMasterStatusesService +type MasterStatusesService interface { + All(req request.MasterStatusesQueryRequest) (masterStatuses []*response.MasterStatusesResponse, paging paginator.Pagination, err error) + Show(id uint) (masterStatuses *response.MasterStatusesResponse, err error) + Save(req request.MasterStatusesCreateRequest) (err error) + Update(id uint, req request.MasterStatusesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewMasterStatusesService init MasterStatusesService +func NewMasterStatusesService(repo repository.MasterStatusesRepository, log zerolog.Logger) MasterStatusesService { + + return &masterStatusesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of MasterStatusesService +func (_i *masterStatusesService) All(req request.MasterStatusesQueryRequest) (masterStatusess []*response.MasterStatusesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + masterStatusess = append(masterStatusess, mapper.MasterStatusesResponseMapper(result)) + } + + return +} + +func (_i *masterStatusesService) Show(id uint) (masterStatuses *response.MasterStatusesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.MasterStatusesResponseMapper(result), nil +} + +func (_i *masterStatusesService) Save(req request.MasterStatusesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *masterStatusesService) Update(id uint, req request.MasterStatusesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *masterStatusesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/provinces/controller/controller.go b/app/module/provinces/controller/controller.go new file mode 100644 index 0000000..3919ee5 --- /dev/null +++ b/app/module/provinces/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/provinces/service" + +type Controller struct { + Provinces ProvincesController +} + +func NewController(ProvincesService service.ProvincesService) *Controller { + return &Controller{ + Provinces: NewProvincesController(ProvincesService), + } +} diff --git a/app/module/provinces/controller/provinces.controller.go b/app/module/provinces/controller/provinces.controller.go new file mode 100644 index 0000000..3e957b9 --- /dev/null +++ b/app/module/provinces/controller/provinces.controller.go @@ -0,0 +1,182 @@ +package controller + +import ( + "campaign-pool-be/app/module/provinces/request" + "campaign-pool-be/app/module/provinces/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type provincesController struct { + provincesService service.ProvincesService +} + +type ProvincesController 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 NewProvincesController(provincesService service.ProvincesService) ProvincesController { + return &provincesController{ + provincesService: provincesService, + } +} + +// All Provinces +// @Summary Get all Provinces +// @Description API for getting all Provinces +// @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 /provinces [get] +func (_i *provincesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.ProvincesQueryRequest + req.Pagination = paginate + + provincesData, paging, err := _i.provincesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces list successfully retrieved"}, + Data: provincesData, + Meta: paging, + }) +} + +// Show Provinces +// @Summary Get one Provinces +// @Description API for getting one Provinces +// @Tags Untags +// @Security Bearer +// @Param id path int true "Provinces 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 /provinces/{id} [get] +func (_i *provincesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + provincesData, err := _i.provincesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully retrieved"}, + Data: provincesData, + }) +} + +// Save Provinces +// @Summary Create Provinces +// @Description API for create Provinces +// @Tags Untags +// @Security Bearer +// @Body request.ProvincesCreateRequest +// @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 /provinces [post] +func (_i *provincesController) Save(c *fiber.Ctx) error { + req := new(request.ProvincesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.provincesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully created"}, + }) +} + +// Update Provinces +// @Summary Update Provinces +// @Description API for update Provinces +// @Tags Untags +// @Security Bearer +// @Body request.ProvincesUpdateRequest +// @Param id path int true "Provinces 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 /provinces/{id} [put] +func (_i *provincesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ProvincesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.provincesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully updated"}, + }) +} + +// Delete Provinces +// @Summary Delete Provinces +// @Description API for delete Provinces +// @Tags Untags +// @Security Bearer +// @Param id path int true "Provinces 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 /provinces/{id} [delete] +func (_i *provincesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.provincesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Provinces successfully deleted"}, + }) +} diff --git a/app/module/provinces/mapper/provinces.mapper.go b/app/module/provinces/mapper/provinces.mapper.go new file mode 100644 index 0000000..0197afa --- /dev/null +++ b/app/module/provinces/mapper/provinces.mapper.go @@ -0,0 +1,19 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/provinces/response" +) + +func ProvincesResponseMapper(provincesReq *entity.Provinces) (provincesRes *res.ProvincesResponse) { + if provincesReq != nil { + provincesRes = &res.ProvincesResponse{ + ID: provincesReq.ID, + ProvName: provincesReq.ProvName, + LocationId: provincesReq.LocationId, + Status: provincesReq.Status, + Timezone: provincesReq.Timezone, + } + } + return provincesRes +} diff --git a/app/module/provinces/provinces.module.go b/app/module/provinces/provinces.module.go new file mode 100644 index 0000000..496be0d --- /dev/null +++ b/app/module/provinces/provinces.module.go @@ -0,0 +1,54 @@ +package provinces + +import ( + "campaign-pool-be/app/module/provinces/controller" + "campaign-pool-be/app/module/provinces/repository" + "campaign-pool-be/app/module/provinces/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ProvincesRouter +type ProvincesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Provinces module +var NewProvincesModule = fx.Options( + // register repository of Provinces module + fx.Provide(repository.NewProvincesRepository), + + // register service of Provinces module + fx.Provide(service.NewProvincesService), + + // register controller of Provinces module + fx.Provide(controller.NewController), + + // register router of Provinces module + fx.Provide(NewProvincesRouter), +) + +// init ProvincesRouter +func NewProvincesRouter(fiber *fiber.App, controller *controller.Controller) *ProvincesRouter { + return &ProvincesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Provinces module +func (_i *ProvincesRouter) RegisterProvincesRoutes() { + // define controllers + provincesController := _i.Controller.Provinces + + // define routes + _i.App.Route("/provinces", func(router fiber.Router) { + router.Get("/", provincesController.All) + router.Get("/:id", provincesController.Show) + router.Post("/", provincesController.Save) + router.Put("/:id", provincesController.Update) + router.Delete("/:id", provincesController.Delete) + }) +} diff --git a/app/module/provinces/repository/provinces.repository.go b/app/module/provinces/repository/provinces.repository.go new file mode 100644 index 0000000..0856655 --- /dev/null +++ b/app/module/provinces/repository/provinces.repository.go @@ -0,0 +1,69 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/provinces/request" + "campaign-pool-be/utils/paginator" +) + +type provincesRepository struct { + DB *database.Database +} + +// ProvincesRepository define interface of IProvincesRepository +type ProvincesRepository interface { + GetAll(req request.ProvincesQueryRequest) (provincess []*entity.Provinces, paging paginator.Pagination, err error) + FindOne(id uint) (provinces *entity.Provinces, err error) + Create(provinces *entity.Provinces) (err error) + Update(id uint, provinces *entity.Provinces) (err error) + Delete(id uint) (err error) +} + +func NewProvincesRepository(db *database.Database) ProvincesRepository { + return &provincesRepository{ + DB: db, + } +} + +// implement interface of IProvincesRepository +func (_i *provincesRepository) GetAll(req request.ProvincesQueryRequest) (provincess []*entity.Provinces, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Provinces{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&provincess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *provincesRepository) FindOne(id uint) (provinces *entity.Provinces, err error) { + if err := _i.DB.DB.First(&provinces, id).Error; err != nil { + return nil, err + } + + return provinces, nil +} + +func (_i *provincesRepository) Create(provinces *entity.Provinces) (err error) { + return _i.DB.DB.Create(provinces).Error +} + +func (_i *provincesRepository) Update(id uint, provinces *entity.Provinces) (err error) { + return _i.DB.DB.Model(&entity.Provinces{}). + Where(&entity.Provinces{ID: id}). + Updates(provinces).Error +} + +func (_i *provincesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.Provinces{}, id).Error +} diff --git a/app/module/provinces/request/provinces.request.go b/app/module/provinces/request/provinces.request.go new file mode 100644 index 0000000..1a25899 --- /dev/null +++ b/app/module/provinces/request/provinces.request.go @@ -0,0 +1,52 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" +) + +type ProvincesGeneric interface { + ToEntity() +} + +type ProvincesQueryRequest struct { + ProvName string `json:"provName" validate:"required"` + LocationId int `json:"locationId" validate:"required"` + Status int `json:"status" validate:"required"` + Timezone string `json:"timezone" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ProvincesCreateRequest struct { + ProvName string `json:"provName" validate:"required"` + LocationId int `json:"locationId" validate:"required"` + Status int `json:"status" validate:"required"` + Timezone string `json:"timezone" validate:"required"` +} + +func (req ProvincesCreateRequest) ToEntity() *entity.Provinces { + return &entity.Provinces{ + ProvName: req.ProvName, + LocationId: req.LocationId, + Status: req.Status, + Timezone: req.Timezone, + } +} + +type ProvincesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + ProvName string `json:"provName" validate:"required"` + LocationId int `json:"locationId" validate:"required"` + Status int `json:"status" validate:"required"` + Timezone string `json:"timezone" validate:"required"` +} + +func (req ProvincesUpdateRequest) ToEntity() *entity.Provinces { + return &entity.Provinces{ + ID: req.ID, + ProvName: req.ProvName, + LocationId: req.LocationId, + Status: req.Status, + Timezone: req.Timezone, + } +} diff --git a/app/module/provinces/response/provinces.response.go b/app/module/provinces/response/provinces.response.go new file mode 100644 index 0000000..aa5b34a --- /dev/null +++ b/app/module/provinces/response/provinces.response.go @@ -0,0 +1,9 @@ +package response + +type ProvincesResponse struct { + ID uint `json:"id"` + ProvName string `json:"provName"` + LocationId int `json:"locationId"` + Status int `json:"status"` + Timezone string `json:"timezone"` +} diff --git a/app/module/provinces/service/provinces.service.go b/app/module/provinces/service/provinces.service.go new file mode 100644 index 0000000..7629427 --- /dev/null +++ b/app/module/provinces/service/provinces.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "campaign-pool-be/app/module/provinces/mapper" + "campaign-pool-be/app/module/provinces/repository" + "campaign-pool-be/app/module/provinces/request" + "campaign-pool-be/app/module/provinces/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// ProvincesService +type provincesService struct { + Repo repository.ProvincesRepository + Log zerolog.Logger +} + +// ProvincesService define interface of IProvincesService +type ProvincesService interface { + All(req request.ProvincesQueryRequest) (provinces []*response.ProvincesResponse, paging paginator.Pagination, err error) + Show(id uint) (provinces *response.ProvincesResponse, err error) + Save(req request.ProvincesCreateRequest) (err error) + Update(id uint, req request.ProvincesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewProvincesService init ProvincesService +func NewProvincesService(repo repository.ProvincesRepository, log zerolog.Logger) ProvincesService { + + return &provincesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of ProvincesService +func (_i *provincesService) All(req request.ProvincesQueryRequest) (provincess []*response.ProvincesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + provincess = append(provincess, mapper.ProvincesResponseMapper(result)) + } + + return +} + +func (_i *provincesService) Show(id uint) (provinces *response.ProvincesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.ProvincesResponseMapper(result), nil +} + +func (_i *provincesService) Save(req request.ProvincesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *provincesService) Update(id uint, req request.ProvincesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *provincesService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/research_journals/controller/controller.go b/app/module/research_journals/controller/controller.go new file mode 100644 index 0000000..26711d9 --- /dev/null +++ b/app/module/research_journals/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/research_journals/service" + +type Controller struct { + ResearchJournals ResearchJournalsController +} + +func NewController(ResearchJournalsService service.ResearchJournalsService) *Controller { + return &Controller{ + ResearchJournals: NewResearchJournalsController(ResearchJournalsService), + } +} diff --git a/app/module/research_journals/controller/research_journals.controller.go b/app/module/research_journals/controller/research_journals.controller.go new file mode 100644 index 0000000..7170000 --- /dev/null +++ b/app/module/research_journals/controller/research_journals.controller.go @@ -0,0 +1,210 @@ +package controller + +import ( + "campaign-pool-be/app/module/research_journals/request" + "campaign-pool-be/app/module/research_journals/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type researchJournalsController struct { + researchJournalsService service.ResearchJournalsService +} + +type ResearchJournalsController 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 NewResearchJournalsController(researchJournalsService service.ResearchJournalsService) ResearchJournalsController { + return &researchJournalsController{ + researchJournalsService: researchJournalsService, + } +} + +// All Research Journals +// @Summary Get all Research Journals +// @Description API for getting all Research Journals for authenticated user +// @Tags Research Journals +// @Security Bearer +// @Param req query request.ResearchJournalsQueryRequest 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 /research-journals [get] +func (_i *researchJournalsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + reqContext := request.ResearchJournalsQueryRequestContext{ + JournalTitle: c.Query("journalTitle"), + Publisher: c.Query("publisher"), + PublishedYear: c.Query("publishedYear"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + // Handle publishedYear filter + if publishedYearStr := c.Query("publishedYear"); publishedYearStr != "" { + if publishedYear, err := strconv.Atoi(publishedYearStr); err == nil { + req.PublishedYear = &publishedYear + } + } + + journalsData, paging, err := _i.researchJournalsService.All(authHeader, req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Research journals list successfully retrieved"}, + Data: journalsData, + Meta: paging, + }) +} + +// Show Research Journal +// @Summary Get one Research Journal +// @Description API for getting one Research Journal +// @Tags Research Journals +// @Security Bearer +// @Param id path int true "Research Journal ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /research-journals/{id} [get] +func (_i *researchJournalsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + journalData, err := _i.researchJournalsService.Show(authHeader, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Research journal successfully retrieved"}, + Data: journalData, + }) +} + +// Save Research Journal +// @Summary Create Research Journal +// @Description API for create Research Journal +// @Tags Research Journals +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.ResearchJournalsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /research-journals [post] +func (_i *researchJournalsController) Save(c *fiber.Ctx) error { + req := new(request.ResearchJournalsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authHeader := c.Get("Authorization") + + dataResult, err := _i.researchJournalsService.Save(authHeader, *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Research journal successfully created"}, + Data: dataResult, + }) +} + +// Update Research Journal +// @Summary Update Research Journal +// @Description API for update Research Journal +// @Tags Research Journals +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Research Journal ID" +// @Param payload body request.ResearchJournalsUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /research-journals/{id} [put] +func (_i *researchJournalsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.ResearchJournalsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authHeader := c.Get("Authorization") + + err = _i.researchJournalsService.Update(authHeader, uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Research journal successfully updated"}, + }) +} + +// Delete Research Journal +// @Summary Delete Research Journal +// @Description API for delete Research Journal +// @Tags Research Journals +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Research Journal ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /research-journals/{id} [delete] +func (_i *researchJournalsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + authHeader := c.Get("Authorization") + + err = _i.researchJournalsService.Delete(authHeader, uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Research journal successfully deleted"}, + }) +} diff --git a/app/module/research_journals/mapper/research_journals.mapper.go b/app/module/research_journals/mapper/research_journals.mapper.go new file mode 100644 index 0000000..3d9ca8f --- /dev/null +++ b/app/module/research_journals/mapper/research_journals.mapper.go @@ -0,0 +1,36 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/research_journals/response" +) + +func ResearchJournalsResponseMapper(researchJournal *entity.ResearchJournals) *response.ResearchJournalsResponse { + result := &response.ResearchJournalsResponse{ + ID: researchJournal.ID, + UserID: researchJournal.UserID, + JournalTitle: researchJournal.JournalTitle, + Publisher: researchJournal.Publisher, + JournalURL: researchJournal.JournalURL, + PublishedDate: researchJournal.PublishedDate, + CreatedAt: researchJournal.CreatedAt, + UpdatedAt: researchJournal.UpdatedAt, + } + + // Extract year from PublishedDate + if researchJournal.PublishedDate != nil { + year := researchJournal.PublishedDate.Year() + result.PublishedYear = &year + } + + if researchJournal.User != nil { + result.User = &response.UserBasicInfo{ + ID: researchJournal.User.ID, + Username: researchJournal.User.Username, + Fullname: researchJournal.User.Fullname, + Email: researchJournal.User.Email, + } + } + + return result +} diff --git a/app/module/research_journals/repository/research_journals.repository.go b/app/module/research_journals/repository/research_journals.repository.go new file mode 100644 index 0000000..5d6ab31 --- /dev/null +++ b/app/module/research_journals/repository/research_journals.repository.go @@ -0,0 +1,84 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/research_journals/request" + "campaign-pool-be/utils/paginator" +) + +type researchJournalsRepository struct { + DB *database.Database +} + +type ResearchJournalsRepository interface { + GetAll(userId uint, req request.ResearchJournalsQueryRequest) (researchJournals []*entity.ResearchJournals, paging paginator.Pagination, err error) + FindOneByUserAndId(userId uint, id uint) (researchJournal *entity.ResearchJournals, err error) + Create(researchJournal *entity.ResearchJournals) (result *entity.ResearchJournals, err error) + Update(userId uint, id uint, researchJournal *entity.ResearchJournals) (err error) + Delete(userId uint, id uint) (err error) +} + +func NewResearchJournalsRepository(db *database.Database) ResearchJournalsRepository { + return &researchJournalsRepository{ + DB: db, + } +} + +func (_i *researchJournalsRepository) GetAll(userId uint, req request.ResearchJournalsQueryRequest) (researchJournals []*entity.ResearchJournals, paging paginator.Pagination, err error) { + query := _i.DB.DB.Where("user_id = ?", userId) + + // Apply filters + if req.JournalTitle != nil { + query = query.Where("journal_title ILIKE ?", "%"+*req.JournalTitle+"%") + } + if req.Publisher != nil { + query = query.Where("publisher ILIKE ?", "%"+*req.Publisher+"%") + } + if req.PublishedYear != nil { + query = query.Where("EXTRACT(YEAR FROM published_date) = ?", *req.PublishedYear) + } + + // Include user relationship + query = query.Preload("User") + + // Order by published date desc (most recent first), then by created_at desc + query = query.Order("published_date DESC NULLS LAST, created_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(&researchJournals).Error + paging = *req.Pagination + + return +} + +func (_i *researchJournalsRepository) FindOneByUserAndId(userId uint, id uint) (researchJournal *entity.ResearchJournals, err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Preload("User").First(&researchJournal).Error + return +} + +func (_i *researchJournalsRepository) Create(researchJournal *entity.ResearchJournals) (result *entity.ResearchJournals, err error) { + err = _i.DB.DB.Create(researchJournal).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Preload("User").First(&result, researchJournal.ID).Error + return +} + +func (_i *researchJournalsRepository) Update(userId uint, id uint, researchJournal *entity.ResearchJournals) (err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Updates(researchJournal).Error + return +} + +func (_i *researchJournalsRepository) Delete(userId uint, id uint) (err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Delete(&entity.ResearchJournals{}).Error + return +} diff --git a/app/module/research_journals/request/research_journals.request.go b/app/module/research_journals/request/research_journals.request.go new file mode 100644 index 0000000..c2594f9 --- /dev/null +++ b/app/module/research_journals/request/research_journals.request.go @@ -0,0 +1,72 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "time" +) + +type ResearchJournalsQueryRequest struct { + JournalTitle *string `json:"journalTitle"` + Publisher *string `json:"publisher"` + PublishedYear *int `json:"publishedYear"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type ResearchJournalsCreateRequest struct { + JournalTitle string `json:"journalTitle" validate:"required,min=2,max=500"` + Publisher string `json:"publisher" validate:"required,min=2,max=255"` + JournalURL string `json:"journalUrl" validate:"required,url"` + PublishedDate *time.Time `json:"publishedDate"` +} + +func (req ResearchJournalsCreateRequest) ToEntity() *entity.ResearchJournals { + return &entity.ResearchJournals{ + JournalTitle: req.JournalTitle, + Publisher: req.Publisher, + JournalURL: req.JournalURL, + PublishedDate: req.PublishedDate, + } +} + +type ResearchJournalsUpdateRequest struct { + JournalTitle string `json:"journalTitle" validate:"required,min=2,max=500"` + Publisher string `json:"publisher" validate:"required,min=2,max=255"` + JournalURL string `json:"journalUrl" validate:"required,url"` + PublishedDate *time.Time `json:"publishedDate"` +} + +func (req ResearchJournalsUpdateRequest) ToEntity() *entity.ResearchJournals { + return &entity.ResearchJournals{ + JournalTitle: req.JournalTitle, + Publisher: req.Publisher, + JournalURL: req.JournalURL, + PublishedDate: req.PublishedDate, + } +} + +type ResearchJournalsQueryRequestContext struct { + JournalTitle string `json:"journalTitle"` + Publisher string `json:"publisher"` + PublishedYear string `json:"publishedYear"` +} + +func (req ResearchJournalsQueryRequestContext) ToParamRequest() ResearchJournalsQueryRequest { + var request ResearchJournalsQueryRequest + + if journalTitle := req.JournalTitle; journalTitle != "" { + request.JournalTitle = &journalTitle + } + if publisher := req.Publisher; publisher != "" { + request.Publisher = &publisher + } + if publishedYearStr := req.PublishedYear; publishedYearStr != "" { + // Extract year from publishedYearStr if it's a date string + if publishedYearStr != "" { + // For now, we'll handle this in the repository layer + // This could be enhanced to parse date strings + } + } + + return request +} diff --git a/app/module/research_journals/research_journals.module.go b/app/module/research_journals/research_journals.module.go new file mode 100644 index 0000000..596e386 --- /dev/null +++ b/app/module/research_journals/research_journals.module.go @@ -0,0 +1,54 @@ +package research_journals + +import ( + "campaign-pool-be/app/module/research_journals/controller" + "campaign-pool-be/app/module/research_journals/repository" + "campaign-pool-be/app/module/research_journals/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of ResearchJournalsRouter +type ResearchJournalsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of ResearchJournals module +var NewResearchJournalsModule = fx.Options( + // register repository of ResearchJournals module + fx.Provide(repository.NewResearchJournalsRepository), + + // register service of ResearchJournals module + fx.Provide(service.NewResearchJournalsService), + + // register controller of ResearchJournals module + fx.Provide(controller.NewController), + + // register router of ResearchJournals module + fx.Provide(NewResearchJournalsRouter), +) + +// init ResearchJournalsRouter +func NewResearchJournalsRouter(fiber *fiber.App, controller *controller.Controller) *ResearchJournalsRouter { + return &ResearchJournalsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of ResearchJournals module +func (_i *ResearchJournalsRouter) RegisterResearchJournalsRoutes() { + // define controllers + researchJournalsController := _i.Controller.ResearchJournals + + // define routes + _i.App.Route("/research-journals", func(router fiber.Router) { + router.Get("/", researchJournalsController.All) + router.Get("/:id", researchJournalsController.Show) + router.Post("/", researchJournalsController.Save) + router.Put("/:id", researchJournalsController.Update) + router.Delete("/:id", researchJournalsController.Delete) + }) +} diff --git a/app/module/research_journals/response/research_journals.response.go b/app/module/research_journals/response/research_journals.response.go new file mode 100644 index 0000000..b5ae911 --- /dev/null +++ b/app/module/research_journals/response/research_journals.response.go @@ -0,0 +1,25 @@ +package response + +import ( + "time" +) + +type ResearchJournalsResponse struct { + ID uint `json:"id"` + UserID uint `json:"userId"` + JournalTitle string `json:"journalTitle"` + Publisher string `json:"publisher"` + JournalURL string `json:"journalUrl"` + PublishedDate *time.Time `json:"publishedDate"` + PublishedYear *int `json:"publishedYear"` // extracted from PublishedDate + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + User *UserBasicInfo `json:"user,omitempty"` +} + +type UserBasicInfo struct { + ID uint `json:"id"` + Username string `json:"username"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} diff --git a/app/module/research_journals/service/research_journals.service.go b/app/module/research_journals/service/research_journals.service.go new file mode 100644 index 0000000..446881d --- /dev/null +++ b/app/module/research_journals/service/research_journals.service.go @@ -0,0 +1,128 @@ +package service + +import ( + "campaign-pool-be/app/module/research_journals/mapper" + "campaign-pool-be/app/module/research_journals/repository" + "campaign-pool-be/app/module/research_journals/request" + "campaign-pool-be/app/module/research_journals/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "errors" + + "github.com/rs/zerolog" +) + +type researchJournalsService struct { + Repo repository.ResearchJournalsRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type ResearchJournalsService interface { + All(authToken string, req request.ResearchJournalsQueryRequest) (researchJournals []*response.ResearchJournalsResponse, paging paginator.Pagination, err error) + Show(authToken string, id uint) (researchJournal *response.ResearchJournalsResponse, err error) + Save(authToken string, req request.ResearchJournalsCreateRequest) (researchJournal *response.ResearchJournalsResponse, err error) + Update(authToken string, id uint, req request.ResearchJournalsUpdateRequest) (err error) + Delete(authToken string, id uint) error +} + +func NewResearchJournalsService(repo repository.ResearchJournalsRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ResearchJournalsService { + return &researchJournalsService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + } +} + +func (_i *researchJournalsService) All(authToken string, req request.ResearchJournalsQueryRequest) (researchJournals []*response.ResearchJournalsResponse, paging paginator.Pagination, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, paginator.Pagination{}, errors.New("unauthorized") + } + + results, paging, err := _i.Repo.GetAll(userInfo.ID, req) + if err != nil { + return + } + + for _, result := range results { + researchJournals = append(researchJournals, mapper.ResearchJournalsResponseMapper(result)) + } + + return +} + +func (_i *researchJournalsService) Show(authToken string, id uint) (researchJournal *response.ResearchJournalsResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("unauthorized") + } + + result, err := _i.Repo.FindOneByUserAndId(userInfo.ID, id) + if err != nil { + return nil, err + } + + return mapper.ResearchJournalsResponseMapper(result), nil +} + +func (_i *researchJournalsService) Save(authToken string, req request.ResearchJournalsCreateRequest) (researchJournal *response.ResearchJournalsResponse, err error) { + _i.Log.Info().Interface("data", req).Msg("Creating research journal") + + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return nil, errors.New("unauthorized") + } + + entity := req.ToEntity() + entity.UserID = userInfo.ID + + result, err := _i.Repo.Create(entity) + if err != nil { + return nil, err + } + + return mapper.ResearchJournalsResponseMapper(result), nil +} + +func (_i *researchJournalsService) Update(authToken string, id uint, req request.ResearchJournalsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("Updating research journal") + + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("unauthorized") + } + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("research journal not found") + } + + entity := req.ToEntity() + return _i.Repo.Update(userInfo.ID, id, entity) +} + +func (_i *researchJournalsService) Delete(authToken string, id uint) error { + _i.Log.Info().Str("authToken", authToken).Uint("id", id).Msg("Deleting research journal") + + userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + if userInfo == nil { + return errors.New("unauthorized") + } + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userInfo.ID, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("research journal not found") + } + + return _i.Repo.Delete(userInfo.ID, id) +} diff --git a/app/module/subscription/controller/controller.go b/app/module/subscription/controller/controller.go new file mode 100644 index 0000000..f35144b --- /dev/null +++ b/app/module/subscription/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/subscription/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + Subscription SubscriptionController +} + +func NewController(SubscriptionService service.SubscriptionService, log zerolog.Logger) *Controller { + return &Controller{ + Subscription: NewSubscriptionController(SubscriptionService, log), + } +} diff --git a/app/module/subscription/controller/subscription.controller.go b/app/module/subscription/controller/subscription.controller.go new file mode 100644 index 0000000..5f9e1b1 --- /dev/null +++ b/app/module/subscription/controller/subscription.controller.go @@ -0,0 +1,193 @@ +package controller + +import ( + "campaign-pool-be/app/module/subscription/request" + "campaign-pool-be/app/module/subscription/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type subscriptionController struct { + subscriptionService service.SubscriptionService + Log zerolog.Logger +} + +type SubscriptionController 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 NewSubscriptionController(subscriptionService service.SubscriptionService, log zerolog.Logger) SubscriptionController { + return &subscriptionController{ + subscriptionService: subscriptionService, + Log: log, + } +} + +// All get all Subscription +// @Summary Get all Subscription +// @Description API for getting all Subscription +// @Tags Subscription +// @Security Bearer +// @Param req query request.SubscriptionQueryRequest 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 /subscription [get] +func (_i *subscriptionController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.SubscriptionQueryRequestContext{ + Email: c.Query("email"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + subscriptionData, paging, err := _i.subscriptionService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription list successfully retrieved"}, + Data: subscriptionData, + Meta: paging, + }) +} + +// Show get one Subscription +// @Summary Get one Subscription +// @Description API for getting one Subscription +// @Tags Subscription +// @Security Bearer +// @Param id path int true "Subscription ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription/{id} [get] +func (_i *subscriptionController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + subscriptionData, err := _i.subscriptionService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully retrieved"}, + Data: subscriptionData, + }) +} + +// Save create Subscription +// @Summary Create Subscription +// @Description API for create Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.SubscriptionCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription [post] +func (_i *subscriptionController) Save(c *fiber.Ctx) error { + req := new(request.SubscriptionCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.subscriptionService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully created"}, + Data: dataResult, + }) +} + +// Update update Subscription +// @Summary update Subscription +// @Description API for update Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.SubscriptionUpdateRequest true "Required payload" +// @Param id path int true "Subscription ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription/{id} [put] +func (_i *subscriptionController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.SubscriptionUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.subscriptionService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully updated"}, + }) +} + +// Delete delete Subscription +// @Summary delete Subscription +// @Description API for delete Subscription +// @Tags Subscription +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Subscription ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /subscription/{id} [delete] +func (_i *subscriptionController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.subscriptionService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Subscription successfully deleted"}, + }) +} diff --git a/app/module/subscription/mapper/subscription.mapper.go b/app/module/subscription/mapper/subscription.mapper.go new file mode 100644 index 0000000..806b0a6 --- /dev/null +++ b/app/module/subscription/mapper/subscription.mapper.go @@ -0,0 +1,19 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/subscription/response" +) + +func SubscriptionResponseMapper(subscriptionReq *entity.Subscription) (subscriptionRes *res.SubscriptionResponse) { + if subscriptionReq != nil { + subscriptionRes = &res.SubscriptionResponse{ + ID: subscriptionReq.ID, + Email: subscriptionReq.Email, + IsActive: subscriptionReq.IsActive, + CreatedAt: subscriptionReq.CreatedAt, + UpdatedAt: subscriptionReq.UpdatedAt, + } + } + return subscriptionRes +} diff --git a/app/module/subscription/repository/subscription.repository.go b/app/module/subscription/repository/subscription.repository.go new file mode 100644 index 0000000..3e241b8 --- /dev/null +++ b/app/module/subscription/repository/subscription.repository.go @@ -0,0 +1,93 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/subscription/request" + "campaign-pool-be/utils/paginator" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type subscriptionRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// SubscriptionRepository define interface of ISubscriptionRepository +type SubscriptionRepository interface { + GetAll(req request.SubscriptionQueryRequest) (subscriptions []*entity.Subscription, paging paginator.Pagination, err error) + FindOne(id uint) (subscription *entity.Subscription, err error) + Create(subscription *entity.Subscription) (subscriptionReturn *entity.Subscription, err error) + Update(id uint, subscription *entity.Subscription) (err error) + Delete(id uint) (err error) +} + +func NewSubscriptionRepository(db *database.Database, logger zerolog.Logger) SubscriptionRepository { + return &subscriptionRepository{ + DB: db, + Log: logger, + } +} + +// implement interface of ISubscriptionRepository +func (_i *subscriptionRepository) GetAll(req request.SubscriptionQueryRequest) (subscriptions []*entity.Subscription, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.Subscription{}) + + query = query.Where("is_active = ?", true) + + if req.Email != nil && *req.Email != "" { + email := strings.ToLower(*req.Email) + query = query.Where("LOWER(email) LIKE ?", "%"+strings.ToLower(email)+"%") + } + 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(&subscriptions).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *subscriptionRepository) FindOne(id uint) (subscription *entity.Subscription, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.First(&subscription).Error; err != nil { + return nil, err + } + + return subscription, nil +} + +func (_i *subscriptionRepository) Create(subscription *entity.Subscription) (subscriptionReturn *entity.Subscription, err error) { + result := _i.DB.DB.Create(subscription) + return subscription, result.Error +} + +func (_i *subscriptionRepository) Update(id uint, subscription *entity.Subscription) (err error) { + query := _i.DB.DB.Model(&entity.Subscription{}).Where(&entity.Subscription{ID: id}) + return query.Updates(subscription).Error +} + +func (_i *subscriptionRepository) Delete(id uint) error { + query := _i.DB.DB.Model(&entity.Subscription{}).Where("id = ?", id) + return query.Delete(&entity.Subscription{}).Error +} diff --git a/app/module/subscription/request/subscription.request.go b/app/module/subscription/request/subscription.request.go new file mode 100644 index 0000000..702d4e9 --- /dev/null +++ b/app/module/subscription/request/subscription.request.go @@ -0,0 +1,53 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "time" +) + +type SubscriptionGeneric interface { + ToEntity() +} + +type SubscriptionQueryRequest struct { + Email *string `json:"email"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type SubscriptionCreateRequest struct { + Email string `json:"email" validate:"required,email"` +} + +func (req SubscriptionCreateRequest) ToEntity() *entity.Subscription { + return &entity.Subscription{ + Email: req.Email, + } +} + +type SubscriptionUpdateRequest struct { + ID uint `json:"id" validate:"required"` + Email string `json:"email" validate:"required,email"` +} + +func (req SubscriptionUpdateRequest) ToEntity() *entity.Subscription { + return &entity.Subscription{ + ID: req.ID, + Email: req.Email, + UpdatedAt: time.Now(), + } +} + +type SubscriptionQueryRequestContext struct { + Email string `json:"email"` +} + +func (req SubscriptionQueryRequestContext) ToParamRequest() SubscriptionQueryRequest { + var request SubscriptionQueryRequest + + if email := req.Email; email != "" { + request.Email = &email + } + + return request +} diff --git a/app/module/subscription/response/subscription.response.go b/app/module/subscription/response/subscription.response.go new file mode 100644 index 0000000..b25d2d1 --- /dev/null +++ b/app/module/subscription/response/subscription.response.go @@ -0,0 +1,11 @@ +package response + +import "time" + +type SubscriptionResponse struct { + ID uint `json:"id"` + Email string `json:"email"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/subscription/service/subscription.service.go b/app/module/subscription/service/subscription.service.go new file mode 100644 index 0000000..bad3045 --- /dev/null +++ b/app/module/subscription/service/subscription.service.go @@ -0,0 +1,87 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/subscription/mapper" + "campaign-pool-be/app/module/subscription/repository" + "campaign-pool-be/app/module/subscription/request" + "campaign-pool-be/app/module/subscription/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// SubscriptionService +type subscriptionService struct { + Repo repository.SubscriptionRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +// SubscriptionService define interface of ISubscriptionService +type SubscriptionService interface { + All(req request.SubscriptionQueryRequest) (subscription []*response.SubscriptionResponse, paging paginator.Pagination, err error) + Show(id uint) (subscription *response.SubscriptionResponse, err error) + Save(req request.SubscriptionCreateRequest) (subscription *entity.Subscription, err error) + Update(id uint, req request.SubscriptionUpdateRequest) (err error) + Delete(id uint) error +} + +// NewSubscriptionService init SubscriptionService +func NewSubscriptionService(repo repository.SubscriptionRepository, log zerolog.Logger, usersRepo usersRepository.UsersRepository) SubscriptionService { + + return &subscriptionService{ + Repo: repo, + Log: log, + UsersRepo: usersRepo, + } +} + +// All implement interface of SubscriptionService +func (_i *subscriptionService) All(req request.SubscriptionQueryRequest) (subscriptions []*response.SubscriptionResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + subscriptions = append(subscriptions, mapper.SubscriptionResponseMapper(result)) + } + + return +} + +func (_i *subscriptionService) Show(id uint) (subscription *response.SubscriptionResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.SubscriptionResponseMapper(result), nil +} + +func (_i *subscriptionService) Save(req request.SubscriptionCreateRequest) (subscription *entity.Subscription, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + return _i.Repo.Create(newReq) +} + +func (_i *subscriptionService) Update(id uint, req request.SubscriptionUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + entity := req.ToEntity() + + return _i.Repo.Update(id, entity) +} + +func (_i *subscriptionService) Delete(id uint) error { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + result.IsActive = false + return _i.Repo.Update(id, result) +} diff --git a/app/module/subscription/subscription.module.go b/app/module/subscription/subscription.module.go new file mode 100644 index 0000000..327786d --- /dev/null +++ b/app/module/subscription/subscription.module.go @@ -0,0 +1,54 @@ +package subscription + +import ( + "campaign-pool-be/app/module/subscription/controller" + "campaign-pool-be/app/module/subscription/repository" + "campaign-pool-be/app/module/subscription/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of SubscriptionRouter +type SubscriptionRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Subscription module +var NewSubscriptionModule = fx.Options( + // register repository of Subscription module + fx.Provide(repository.NewSubscriptionRepository), + + // register service of Subscription module + fx.Provide(service.NewSubscriptionService), + + // register controller of Subscription module + fx.Provide(controller.NewController), + + // register router of Subscription module + fx.Provide(NewSubscriptionRouter), +) + +// init SubscriptionRouter +func NewSubscriptionRouter(fiber *fiber.App, controller *controller.Controller) *SubscriptionRouter { + return &SubscriptionRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Subscription module +func (_i *SubscriptionRouter) RegisterSubscriptionRoutes() { + // define controllers + subscriptionController := _i.Controller.Subscription + + // define routes + _i.App.Route("/subscription", func(router fiber.Router) { + router.Get("/", subscriptionController.All) + router.Get("/:id", subscriptionController.Show) + router.Post("/", subscriptionController.Save) + router.Put("/:id", subscriptionController.Update) + router.Delete("/:id", subscriptionController.Delete) + }) +} diff --git a/app/module/user_levels/controller/controller.go b/app/module/user_levels/controller/controller.go new file mode 100644 index 0000000..fe6ba77 --- /dev/null +++ b/app/module/user_levels/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/user_levels/service" + +type Controller struct { + UserLevels UserLevelsController +} + +func NewController(UserLevelsService service.UserLevelsService) *Controller { + return &Controller{ + UserLevels: NewUserLevelsController(UserLevelsService), + } +} diff --git a/app/module/user_levels/controller/user_levels.controller.go b/app/module/user_levels/controller/user_levels.controller.go new file mode 100644 index 0000000..0cea243 --- /dev/null +++ b/app/module/user_levels/controller/user_levels.controller.go @@ -0,0 +1,257 @@ +package controller + +import ( + "campaign-pool-be/app/module/user_levels/request" + "campaign-pool-be/app/module/user_levels/service" + "campaign-pool-be/utils/paginator" + "strconv" + "strings" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type userLevelsController struct { + userLevelsService service.UserLevelsService +} + +type UserLevelsController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByAlias(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + EnableApproval(c *fiber.Ctx) error +} + +func NewUserLevelsController(userLevelsService service.UserLevelsService) UserLevelsController { + return &userLevelsController{ + userLevelsService: userLevelsService, + } +} + +// All UserLevels +// @Summary Get all UserLevels +// @Description API for getting all UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param req query request.UserLevelsQueryRequest 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 /user-levels [get] +func (_i *userLevelsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UserLevelsQueryRequestContext{ + Name: c.Query("name"), + LevelNumber: c.Query("levelNumber"), + ParentLevelId: c.Query("parentLevelId"), + ProvinceId: c.Query("provinceId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + userLevelsData, paging, err := _i.userLevelsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels list successfully retrieved"}, + Data: userLevelsData, + Meta: paging, + }) +} + +// Show UserLevels +// @Summary Get one UserLevels +// @Description API for getting one UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param id path int true "UserLevels ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/{id} [get] +func (_i *userLevelsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userLevelsData, err := _i.userLevelsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserLevels successfully retrieved"}, + Data: userLevelsData, + }) +} + +// ShowByAlias UserLevels +// @Summary Get one UserLevels +// @Description API for getting one UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param alias path string true "UserLevels Alias" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/alias/{alias} [get] +func (_i *userLevelsController) ShowByAlias(c *fiber.Ctx) error { + alias := c.Params("alias") + userLevelsData, err := _i.userLevelsService.ShowByAlias(alias) + if err != nil { + return err + } + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserLevels successfully retrieved"}, + Data: userLevelsData, + }) +} + +// Save UserLevels +// @Summary Create UserLevels +// @Description API for create UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserLevelsCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels [post] +func (_i *userLevelsController) Save(c *fiber.Ctx) error { + req := new(request.UserLevelsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.userLevelsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels successfully created"}, + Data: dataResult, + }) +} + +// Update UserLevels +// @Summary update UserLevels +// @Description API for update UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserLevelsUpdateRequest true "Required payload" +// @Param id path int true "UserLevels ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/{id} [put] +func (_i *userLevelsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserLevelsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userLevelsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels successfully updated"}, + }) +} + +// Delete UserLevels +// @Summary delete UserLevels +// @Description API for delete UserLevels +// @Tags UserLevels +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "UserLevels 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 /user-levels/{id} [delete] +func (_i *userLevelsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userLevelsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserLevels successfully deleted"}, + }) +} + +// EnableApproval Articles +// @Summary EnableApproval Articles +// @Description API for Enable Approval of Article +// @Tags UserLevels +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserLevelsApprovalRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-levels/enable-approval [post] +func (_i *userLevelsController) EnableApproval(c *fiber.Ctx) error { + req := new(request.UserLevelsApprovalRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + ids := strings.Split(req.Ids, ",") + for _, id := range ids { + idUint, err := strconv.ParseUint(id, 10, 64) + if err != nil { + return err + } + err = _i.userLevelsService.EnableApproval(uint(idUint), req.IsApprovalActive) + if err != nil { + return err + } + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"isApprovalActive of UserLevels successfully saved"}, + }) +} diff --git a/app/module/user_levels/mapper/user_levels.mapper.go b/app/module/user_levels/mapper/user_levels.mapper.go new file mode 100644 index 0000000..dcb3739 --- /dev/null +++ b/app/module/user_levels/mapper/user_levels.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity/user_levels" + res "campaign-pool-be/app/module/user_levels/response" +) + +func UserLevelsResponseMapper(userLevelsReq *user_levels.UserLevels) (userLevelsRes *res.UserLevelsResponse) { + if userLevelsReq != nil { + userLevelsRes = &res.UserLevelsResponse{ + ID: userLevelsReq.ID, + Name: userLevelsReq.Name, + AliasName: userLevelsReq.AliasName, + LevelNumber: userLevelsReq.LevelNumber, + ParentLevelId: userLevelsReq.ParentLevelId, + ProvinceId: userLevelsReq.ProvinceId, + IsApprovalActive: userLevelsReq.IsApprovalActive, + Group: userLevelsReq.Group, + IsActive: userLevelsReq.IsActive, + CreatedAt: userLevelsReq.CreatedAt, + UpdatedAt: userLevelsReq.UpdatedAt, + } + } + return userLevelsRes +} diff --git a/app/module/user_levels/repository/user_levels.repository.go b/app/module/user_levels/repository/user_levels.repository.go new file mode 100644 index 0000000..a930205 --- /dev/null +++ b/app/module/user_levels/repository/user_levels.repository.go @@ -0,0 +1,109 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity/user_levels" + "campaign-pool-be/app/module/user_levels/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" +) + +type userLevelsRepository struct { + DB *database.Database +} + +// UserLevelsRepository define interface of IUserLevelsRepository +type UserLevelsRepository interface { + GetAll(req request.UserLevelsQueryRequest) (userLevelss []*user_levels.UserLevels, paging paginator.Pagination, err error) + FindOne(id uint) (userLevels *user_levels.UserLevels, err error) + FindOneByAlias(alias string) (userLevels *user_levels.UserLevels, err error) + Create(userLevels *user_levels.UserLevels) (userLevelsReturn *user_levels.UserLevels, err error) + Update(id uint, userLevels *user_levels.UserLevels) (err error) + Delete(id uint) (err error) +} + +func NewUserLevelsRepository(db *database.Database) UserLevelsRepository { + return &userLevelsRepository{ + DB: db, + } +} + +// implement interface of IUserLevelsRepository +func (_i *userLevelsRepository) GetAll(req request.UserLevelsQueryRequest) (userLevelss []*user_levels.UserLevels, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&user_levels.UserLevels{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.LevelNumber != nil { + query = query.Where("level_number = ?", req.LevelNumber) + } + if req.ParentLevelId != nil { + query = query.Where("parent_level_id = ?", req.ParentLevelId) + } + if req.ProvinceId != nil { + query = query.Where("province_id = ?", req.ProvinceId) + } + 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(&userLevelss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userLevelsRepository) FindOne(id uint) (userLevels *user_levels.UserLevels, err error) { + if err := _i.DB.DB.First(&userLevels, id).Error; err != nil { + return nil, err + } + + return userLevels, nil +} + +func (_i *userLevelsRepository) FindOneByAlias(alias string) (userLevels *user_levels.UserLevels, err error) { + if err := _i.DB.DB.Where("alias_name = ?", strings.ToLower(alias)).First(&userLevels).Error; err != nil { + return nil, err + } + + return userLevels, nil +} + +func (_i *userLevelsRepository) Create(userLevels *user_levels.UserLevels) (userLevelsReturn *user_levels.UserLevels, err error) { + result := _i.DB.DB.Create(userLevels) + return userLevels, result.Error +} + +func (_i *userLevelsRepository) Update(id uint, userLevels *user_levels.UserLevels) (err error) { + userLevelsMap, err := utilSvc.StructToMap(userLevels) + if err != nil { + return err + } + return _i.DB.DB.Model(&user_levels.UserLevels{}). + Where(&user_levels.UserLevels{ID: id}). + Updates(userLevelsMap).Error +} + +func (_i *userLevelsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&user_levels.UserLevels{}, id).Error +} diff --git a/app/module/user_levels/repository/user_levels.repository.go~ b/app/module/user_levels/repository/user_levels.repository.go~ new file mode 100644 index 0000000..deaec9f --- /dev/null +++ b/app/module/user_levels/repository/user_levels.repository.go~ @@ -0,0 +1,109 @@ +package repository + +import ( + "fmt" + "jaecoo-be/app/database" + "jaecoo-be/app/database/entity" + "jaecoo-be/app/module/user_levels/request" + "jaecoo-be/utils/paginator" + utilSvc "jaecoo-be/utils/service" + "strings" +) + +type userLevelsRepository struct { + DB *database.Database +} + +// UserLevelsRepository define interface of IUserLevelsRepository +type UserLevelsRepository interface { + GetAll(req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error) + FindOne(id uint) (userLevels *entity.UserLevels, err error) + FindOneByAlias(alias string) (userLevels *entity.UserLevels, err error) + Create(userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error) + Update(id uint, userLevels *entity.UserLevels) (err error) + Delete(id uint) (err error) +} + +func NewUserLevelsRepository(db *database.Database) UserLevelsRepository { + return &userLevelsRepository{ + DB: db, + } +} + +// implement interface of IUserLevelsRepository +func (_i *userLevelsRepository) GetAll(req request.UserLevelsQueryRequest) (userLevelss []*entity.UserLevels, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.UserLevels{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.LevelNumber != nil { + query = query.Where("level_number = ?", req.LevelNumber) + } + if req.ParentLevelId != nil { + query = query.Where("parent_level_id = ?", req.ParentLevelId) + } + if req.ProvinceId != nil { + query = query.Where("province_id = ?", req.ProvinceId) + } + 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(&userLevelss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userLevelsRepository) FindOne(id uint) (userLevels *entity.UserLevels, err error) { + if err := _i.DB.DB.First(&userLevels, id).Error; err != nil { + return nil, err + } + + return userLevels, nil +} + +func (_i *userLevelsRepository) FindOneByAlias(alias string) (userLevels *entity.UserLevels, err error) { + if err := _i.DB.DB.Where("alias_name = ?", strings.ToLower(alias)).First(&userLevels).Error; err != nil { + return nil, err + } + + return userLevels, nil +} + +func (_i *userLevelsRepository) Create(userLevels *entity.UserLevels) (userLevelsReturn *entity.UserLevels, err error) { + result := _i.DB.DB.Create(userLevels) + return userLevels, result.Error +} + +func (_i *userLevelsRepository) Update(id uint, userLevels *entity.UserLevels) (err error) { + userLevelsMap, err := utilSvc.StructToMap(userLevels) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.UserLevels{}). + Where(&entity.UserLevels{ID: id}). + Updates(userLevelsMap).Error +} + +func (_i *userLevelsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserLevels{}, id).Error +} diff --git a/app/module/user_levels/request/user_levels.request.go b/app/module/user_levels/request/user_levels.request.go new file mode 100644 index 0000000..2aa12bb --- /dev/null +++ b/app/module/user_levels/request/user_levels.request.go @@ -0,0 +1,107 @@ +package request + +import ( + "campaign-pool-be/app/database/entity/user_levels" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type UserLevelsGeneric interface { + ToEntity() +} + +type UserLevelsQueryRequest struct { + Name *string `json:"name"` + LevelNumber *int `json:"levelNumber"` + ParentLevelId *int `json:"parentLevelId"` + ProvinceId *int `json:"provinceId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserLevelsCreateRequest struct { + Name string `json:"name" validate:"required"` + AliasName string `json:"aliasName" validate:"required"` + LevelNumber int `json:"levelNumber" validate:"required"` + ParentLevelId *int `json:"parentLevelId"` + ProvinceId *int `json:"provinceId"` + Group *string `json:"group"` + IsApprovalActive *bool `json:"isApprovalActive"` + IsActive *bool `json:"isActive"` +} + +func (req UserLevelsCreateRequest) ToEntity() *user_levels.UserLevels { + return &user_levels.UserLevels{ + Name: req.Name, + AliasName: req.AliasName, + LevelNumber: req.LevelNumber, + ParentLevelId: req.ParentLevelId, + ProvinceId: req.ProvinceId, + IsApprovalActive: req.IsApprovalActive, + Group: req.Group, + IsActive: req.IsActive, + } +} + +type UserLevelsUpdateRequest struct { + Name string `json:"name" validate:"required"` + AliasName string `json:"aliasName" validate:"required"` + LevelNumber int `json:"levelNumber" validate:"required"` + ParentLevelId *int `json:"parentLevelId"` + IsApprovalActive *bool `json:"isApprovalActive"` + Group *string `json:"group"` + ProvinceId *int `json:"provinceId"` +} + +func (req UserLevelsUpdateRequest) ToEntity() *user_levels.UserLevels { + return &user_levels.UserLevels{ + Name: req.Name, + AliasName: req.AliasName, + LevelNumber: req.LevelNumber, + ParentLevelId: req.ParentLevelId, + ProvinceId: req.ProvinceId, + IsApprovalActive: req.IsApprovalActive, + Group: req.Group, + UpdatedAt: time.Now(), + } +} + +type UserLevelsApprovalRequest struct { + Ids string `json:"ids" validate:"required"` + IsApprovalActive bool `json:"isApprovalActive" validate:"required"` +} + +type UserLevelsQueryRequestContext struct { + Name string `json:"name"` + LevelNumber string `json:"levelNumber"` + ParentLevelId string `json:"parentLevelId"` + ProvinceId string `json:"provinceId"` +} + +func (req UserLevelsQueryRequestContext) ToParamRequest() UserLevelsQueryRequest { + var request UserLevelsQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if levelNumberStr := req.LevelNumber; levelNumberStr != "" { + LevelNumber, err := strconv.Atoi(levelNumberStr) + if err == nil { + request.LevelNumber = &LevelNumber + } + } + if parentLevelIdStr := req.ParentLevelId; parentLevelIdStr != "" { + parentLevelId, err := strconv.Atoi(parentLevelIdStr) + if err == nil { + request.ParentLevelId = &parentLevelId + } + } + if provinceIdStr := req.ProvinceId; provinceIdStr != "" { + provinceId, err := strconv.Atoi(provinceIdStr) + if err == nil { + request.ProvinceId = &provinceId + } + } + + return request +} diff --git a/app/module/user_levels/response/user_levels.response.go b/app/module/user_levels/response/user_levels.response.go new file mode 100644 index 0000000..cdc2956 --- /dev/null +++ b/app/module/user_levels/response/user_levels.response.go @@ -0,0 +1,17 @@ +package response + +import "time" + +type UserLevelsResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + AliasName string `json:"aliasName"` + LevelNumber int `json:"levelNumber"` + ParentLevelId *int `json:"parentLevelId"` + ProvinceId *int `json:"provinceId"` + IsApprovalActive *bool `json:"isApprovalActive"` + Group *string `json:"group"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/user_levels/service/user_levels.service.go b/app/module/user_levels/service/user_levels.service.go new file mode 100644 index 0000000..31ee86a --- /dev/null +++ b/app/module/user_levels/service/user_levels.service.go @@ -0,0 +1,111 @@ +package service + +import ( + "campaign-pool-be/app/database/entity/user_levels" + "campaign-pool-be/app/module/user_levels/mapper" + "campaign-pool-be/app/module/user_levels/repository" + "campaign-pool-be/app/module/user_levels/request" + "campaign-pool-be/app/module/user_levels/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// UserLevelsService +type userLevelsService struct { + Repo repository.UserLevelsRepository + Log zerolog.Logger +} + +// UserLevelsService define interface of IUserLevelsService +type UserLevelsService interface { + All(req request.UserLevelsQueryRequest) (userLevels []*response.UserLevelsResponse, paging paginator.Pagination, err error) + Show(id uint) (userLevels *response.UserLevelsResponse, err error) + ShowByAlias(alias string) (userLevels *response.UserLevelsResponse, err error) + Save(req request.UserLevelsCreateRequest) (userLevels *user_levels.UserLevels, err error) + Update(id uint, req request.UserLevelsUpdateRequest) (err error) + Delete(id uint) error + EnableApproval(id uint, isApprovalActive bool) (err error) +} + +// NewUserLevelsService init UserLevelsService +func NewUserLevelsService(repo repository.UserLevelsRepository, log zerolog.Logger) UserLevelsService { + + return &userLevelsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of UserLevelsService +func (_i *userLevelsService) All(req request.UserLevelsQueryRequest) (userLevelss []*response.UserLevelsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + userLevelss = append(userLevelss, mapper.UserLevelsResponseMapper(result)) + } + + return +} + +func (_i *userLevelsService) Show(id uint) (userLevels *response.UserLevelsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserLevelsResponseMapper(result), nil +} + +func (_i *userLevelsService) ShowByAlias(alias string) (userLevels *response.UserLevelsResponse, err error) { + result, err := _i.Repo.FindOneByAlias(alias) + if err != nil { + return nil, err + } + + return mapper.UserLevelsResponseMapper(result), nil +} + +func (_i *userLevelsService) Save(req request.UserLevelsCreateRequest) (userLevels *user_levels.UserLevels, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + saveUserLevelsRes, err := _i.Repo.Create(req.ToEntity()) + if err != nil { + return nil, err + } + + return saveUserLevelsRes, nil +} + +func (_i *userLevelsService) Update(id uint, req request.UserLevelsUpdateRequest) (err error) { + //_i.Log.Info().Interface("data", req).Msg("") + + _i.Log.Info().Interface("data", req.ToEntity()).Msg("") + + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userLevelsService) 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 *userLevelsService) EnableApproval(id uint, isApprovalActive bool) (err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + *result.IsApprovalActive = isApprovalActive + + return _i.Repo.Update(id, result) +} diff --git a/app/module/user_levels/user_levels.module.go b/app/module/user_levels/user_levels.module.go new file mode 100644 index 0000000..a695472 --- /dev/null +++ b/app/module/user_levels/user_levels.module.go @@ -0,0 +1,56 @@ +package user_levels + +import ( + "campaign-pool-be/app/module/user_levels/controller" + "campaign-pool-be/app/module/user_levels/repository" + "campaign-pool-be/app/module/user_levels/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of UserLevelsRouter +type UserLevelsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserLevels module +var NewUserLevelsModule = fx.Options( + // register repository of UserLevels module + fx.Provide(repository.NewUserLevelsRepository), + + // register service of UserLevels module + fx.Provide(service.NewUserLevelsService), + + // register controller of UserLevels module + fx.Provide(controller.NewController), + + // register router of UserLevels module + fx.Provide(NewUserLevelsRouter), +) + +// init UserLevelsRouter +func NewUserLevelsRouter(fiber *fiber.App, controller *controller.Controller) *UserLevelsRouter { + return &UserLevelsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserLevels module +func (_i *UserLevelsRouter) RegisterUserLevelsRoutes() { + // define controllers + userLevelsController := _i.Controller.UserLevels + + // define routes + _i.App.Route("/user-levels", func(router fiber.Router) { + router.Get("/", userLevelsController.All) + router.Get("/:id", userLevelsController.Show) + router.Get("/alias/:alias", userLevelsController.ShowByAlias) + router.Post("/", userLevelsController.Save) + router.Put("/:id", userLevelsController.Update) + router.Delete("/:id", userLevelsController.Delete) + router.Put("/approval/:id", userLevelsController.EnableApproval) + }) +} diff --git a/app/module/user_role_accesses/controller/controller.go b/app/module/user_role_accesses/controller/controller.go new file mode 100644 index 0000000..757152d --- /dev/null +++ b/app/module/user_role_accesses/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/user_role_accesses/service" + +type Controller struct { + UserRoleAccesses UserRoleAccessesController +} + +func NewController(UserRoleAccessesService service.UserRoleAccessesService) *Controller { + return &Controller{ + UserRoleAccesses: NewUserRoleAccessesController(UserRoleAccessesService), + } +} diff --git a/app/module/user_role_accesses/controller/user_role_accesses.controller.go b/app/module/user_role_accesses/controller/user_role_accesses.controller.go new file mode 100644 index 0000000..e303257 --- /dev/null +++ b/app/module/user_role_accesses/controller/user_role_accesses.controller.go @@ -0,0 +1,191 @@ +package controller + +import ( + "campaign-pool-be/app/module/user_role_accesses/request" + "campaign-pool-be/app/module/user_role_accesses/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type userRoleAccessesController struct { + userRoleAccessesService service.UserRoleAccessesService +} + +type UserRoleAccessesController 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 NewUserRoleAccessesController(userRoleAccessesService service.UserRoleAccessesService) UserRoleAccessesController { + return &userRoleAccessesController{ + userRoleAccessesService: userRoleAccessesService, + } +} + +// All UserRoleAccesses +// @Summary Get all UserRoleAccesses +// @Description API for getting all UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param req query request.UserRoleAccessesQueryRequest false "query parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses [get] +func (_i *userRoleAccessesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UserRoleAccessesQueryRequestContext{ + MenuId: c.Query("menuId"), + UserRoleId: c.Query("userRoleId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + userRoleAccessesData, paging, err := _i.userRoleAccessesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses list successfully retrieved"}, + Data: userRoleAccessesData, + Meta: paging, + }) +} + +// Show UserRoleAccesses +// @Summary Get one UserRoleAccesses +// @Description API for getting one UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param id path int true "UserRoleAccesses ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses/{id} [get] +func (_i *userRoleAccessesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userRoleAccessesData, err := _i.userRoleAccessesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully retrieved"}, + Data: userRoleAccessesData, + }) +} + +// Save UserRoleAccesses +// @Summary Create UserRoleAccesses +// @Description API for create UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserRoleAccessesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses [post] +func (_i *userRoleAccessesController) Save(c *fiber.Ctx) error { + req := new(request.UserRoleAccessesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.userRoleAccessesService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully created"}, + }) +} + +// Update UserRoleAccesses +// @Summary update UserRoleAccesses +// @Description API for update UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserRoleAccessesUpdateRequest true "Required payload" +// @Param id path int true "UserRoleAccesses ID" +// @Success 200 {object} response.Response +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses/{id} [put] +func (_i *userRoleAccessesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserRoleAccessesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userRoleAccessesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully updated"}, + }) +} + +// Delete UserRoleAccesses +// @Summary delete UserRoleAccesses +// @Description API for delete UserRoleAccesses +// @Tags UserRoleAccesses +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "UserRoleAccesses ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-role-accesses/{id} [delete] +func (_i *userRoleAccessesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userRoleAccessesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoleAccesses successfully deleted"}, + }) +} diff --git a/app/module/user_role_accesses/entity/user_role_accesses.entity.go b/app/module/user_role_accesses/entity/user_role_accesses.entity.go new file mode 100644 index 0000000..257a0f1 --- /dev/null +++ b/app/module/user_role_accesses/entity/user_role_accesses.entity.go @@ -0,0 +1,18 @@ +package entity + +import "time" + +type UserRoleAccesses struct { + ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` + UserRoleId int `json:"userRoleId" gorm:"type:int4"` + MenuId int `json:"menuId" gorm:"type:int4"` + IsViewEnabled bool `json:"isViewEnabled" gorm:"type:bool"` + IsInsertEnabled bool `json:"isInsertEnabled" gorm:"type:bool"` + IsUpdateEnabled bool `json:"isUpdateEnabled" gorm:"type:bool"` + IsDeleteEnabled bool `json:"isDeleteEnabled" gorm:"type:bool"` + IsApprovalEnabled bool `json:"isApprovalEnabled" gorm:"type:bool"` + IsAdminEnabled bool `json:"isAdminEnabled" gorm:"type:bool"` + IsActive bool `json:"isActive" gorm:"type:bool"` + CreatedAt time.Time `json:"createdAt" gorm:"default:now()"` + UpdatedAt time.Time `json:"updatedAt" gorm:"default:now()"` +} diff --git a/app/module/user_role_accesses/mapper/user_role_accesses.mapper.go b/app/module/user_role_accesses/mapper/user_role_accesses.mapper.go new file mode 100644 index 0000000..d331ff7 --- /dev/null +++ b/app/module/user_role_accesses/mapper/user_role_accesses.mapper.go @@ -0,0 +1,26 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/user_role_accesses/response" +) + +func UserRoleAccessesResponseMapper(userRoleAccessesReq *entity.UserRoleAccesses) (userRoleAccessesRes *res.UserRoleAccessesResponse) { + if userRoleAccessesReq != nil { + userRoleAccessesRes = &res.UserRoleAccessesResponse{ + ID: userRoleAccessesReq.ID, + UserRoleId: userRoleAccessesReq.UserRoleId, + MenuId: userRoleAccessesReq.MenuId, + IsViewEnabled: userRoleAccessesReq.IsViewEnabled, + IsInsertEnabled: userRoleAccessesReq.IsInsertEnabled, + IsUpdateEnabled: userRoleAccessesReq.IsUpdateEnabled, + IsDeleteEnabled: userRoleAccessesReq.IsDeleteEnabled, + IsApprovalEnabled: userRoleAccessesReq.IsApprovalEnabled, + IsAdminEnabled: userRoleAccessesReq.IsAdminEnabled, + IsActive: userRoleAccessesReq.IsActive, + CreatedAt: userRoleAccessesReq.CreatedAt, + UpdatedAt: userRoleAccessesReq.UpdatedAt, + } + } + return userRoleAccessesRes +} diff --git a/app/module/user_role_accesses/repository/user_role_accesses.repository.go b/app/module/user_role_accesses/repository/user_role_accesses.repository.go new file mode 100644 index 0000000..ebe2473 --- /dev/null +++ b/app/module/user_role_accesses/repository/user_role_accesses.repository.go @@ -0,0 +1,82 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/user_role_accesses/request" + "campaign-pool-be/utils/paginator" +) + +type userRoleAccessesRepository struct { + DB *database.Database +} + +// UserRoleAccessesRepository define interface of IUserRoleAccessesRepository +type UserRoleAccessesRepository interface { + GetAll(req request.UserRoleAccessesQueryRequest) (userRoleAccessess []*entity.UserRoleAccesses, paging paginator.Pagination, err error) + FindOne(id uint) (userRoleAccesses *entity.UserRoleAccesses, err error) + Create(userRoleAccesses *entity.UserRoleAccesses) (err error) + CreateAll(userRoleAccesses *[]entity.UserRoleAccesses) (err error) + Update(id uint, userRoleAccesses *entity.UserRoleAccesses) (err error) + Delete(id uint) (err error) +} + +func NewUserRoleAccessesRepository(db *database.Database) UserRoleAccessesRepository { + return &userRoleAccessesRepository{ + DB: db, + } +} + +// implement interface of IUserRoleAccessesRepository +func (_i *userRoleAccessesRepository) GetAll(req request.UserRoleAccessesQueryRequest) (userRoleAccessess []*entity.UserRoleAccesses, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.UserRoleAccesses{}) + query = query.Where("is_active = ?", true) + + if req.UserRoleId != nil { + query = query.Where("user_role_id = ?", req.UserRoleId) + } + if req.MenuId != nil { + query = query.Where("menu_id = ?", req.MenuId) + } + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userRoleAccessess).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userRoleAccessesRepository) FindOne(id uint) (userRoleAccesses *entity.UserRoleAccesses, err error) { + if err := _i.DB.DB.First(&userRoleAccesses, id).Error; err != nil { + return nil, err + } + + return userRoleAccesses, nil +} + +func (_i *userRoleAccessesRepository) Create(userRoleAccesses *entity.UserRoleAccesses) (err error) { + return _i.DB.DB.Create(userRoleAccesses).Error +} + +func (_i *userRoleAccessesRepository) CreateAll(userRoleAccesses *[]entity.UserRoleAccesses) (err error) { + return _i.DB.DB.Create(userRoleAccesses).Error +} + +func (_i *userRoleAccessesRepository) Update(id uint, userRoleAccesses *entity.UserRoleAccesses) (err error) { + return _i.DB.DB.Model(&entity.UserRoleAccesses{}). + Where(&entity.UserRoleAccesses{ID: id}). + Updates(userRoleAccesses).Error +} + +func (_i *userRoleAccessesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserRoleAccesses{}, id).Error +} diff --git a/app/module/user_role_accesses/request/user_role_accesses.request.go b/app/module/user_role_accesses/request/user_role_accesses.request.go new file mode 100644 index 0000000..30e76bd --- /dev/null +++ b/app/module/user_role_accesses/request/user_role_accesses.request.go @@ -0,0 +1,93 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type UserRoleAccessesGeneric interface { + ToEntity() +} + +type UserRoleAccessesQueryRequest struct { + UserRoleId *int `json:"userRoleId" validate:"required"` + MenuId *int `json:"menuId" validate:"required"` + IsActive *bool `json:"isActive" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserRoleAccessesCreateRequest struct { + MenuId int `json:"menuId" validate:"required"` + IsViewEnabled bool `json:"isViewEnabled" validate:"required"` + IsInsertEnabled bool `json:"isInsertEnabled" validate:"required"` + IsUpdateEnabled bool `json:"isUpdateEnabled" validate:"required"` + IsDeleteEnabled bool `json:"isDeleteEnabled" validate:"required"` + IsApprovalEnabled bool `json:"isApprovalEnabled" validate:"required"` + IsAdminEnabled bool `json:"isAdminEnabled" validate:"required"` +} + +func (req UserRoleAccessesCreateRequest) ToEntity() *entity.UserRoleAccesses { + return &entity.UserRoleAccesses{ + MenuId: req.MenuId, + IsViewEnabled: req.IsViewEnabled, + IsInsertEnabled: req.IsInsertEnabled, + IsUpdateEnabled: req.IsUpdateEnabled, + IsDeleteEnabled: req.IsDeleteEnabled, + IsApprovalEnabled: req.IsApprovalEnabled, + IsAdminEnabled: req.IsAdminEnabled, + } +} + +type UserRoleAccessesUpdateRequest struct { + ID uint `json:"id" validate:"required"` + UserRoleId uint `json:"userRoleId" validate:"required"` + MenuId int `json:"menuId" validate:"required"` + IsViewEnabled bool `json:"isViewEnabled" validate:"required"` + IsInsertEnabled bool `json:"isInsertEnabled" validate:"required"` + IsUpdateEnabled bool `json:"isUpdateEnabled" validate:"required"` + IsDeleteEnabled bool `json:"isDeleteEnabled" validate:"required"` + IsApprovalEnabled bool `json:"isApprovalEnabled" validate:"required"` + IsAdminEnabled bool `json:"isAdminEnabled" validate:"required"` +} + +func (req UserRoleAccessesUpdateRequest) ToEntity() *entity.UserRoleAccesses { + return &entity.UserRoleAccesses{ + ID: req.ID, + UserRoleId: req.UserRoleId, + MenuId: req.MenuId, + IsViewEnabled: req.IsViewEnabled, + IsInsertEnabled: req.IsInsertEnabled, + IsUpdateEnabled: req.IsUpdateEnabled, + IsDeleteEnabled: req.IsDeleteEnabled, + IsApprovalEnabled: req.IsApprovalEnabled, + IsAdminEnabled: req.IsAdminEnabled, + UpdatedAt: time.Now(), + } +} + +type UserRoleAccessesQueryRequestContext struct { + UserRoleId string `json:"userRoleId"` + MenuId string `json:"menuId"` +} + +func (req UserRoleAccessesQueryRequestContext) ToParamRequest() UserRoleAccessesQueryRequest { + var request UserRoleAccessesQueryRequest + + if userRoleIdStr := req.UserRoleId; userRoleIdStr != "" { + userRoleId, err := strconv.Atoi(userRoleIdStr) + if err == nil { + request.UserRoleId = &userRoleId + } + } + + if menuIdStr := req.MenuId; menuIdStr != "" { + menuId, err := strconv.Atoi(menuIdStr) + if err == nil { + request.MenuId = &menuId + } + } + + return request +} diff --git a/app/module/user_role_accesses/response/user_role_accesses.response.go b/app/module/user_role_accesses/response/user_role_accesses.response.go new file mode 100644 index 0000000..74578dc --- /dev/null +++ b/app/module/user_role_accesses/response/user_role_accesses.response.go @@ -0,0 +1,18 @@ +package response + +import "time" + +type UserRoleAccessesResponse struct { + ID uint `json:"id"` + UserRoleId uint `json:"userRoleId"` + MenuId int `json:"menuId"` + IsViewEnabled bool `json:"isViewEnabled"` + IsInsertEnabled bool `json:"isInsertEnabled"` + IsUpdateEnabled bool `json:"isUpdateEnabled"` + IsDeleteEnabled bool `json:"isDeleteEnabled"` + IsApprovalEnabled bool `json:"isApprovalEnabled"` + IsAdminEnabled bool `json:"isAdminEnabled"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/user_role_accesses/service/user_role_accesses.service.go b/app/module/user_role_accesses/service/user_role_accesses.service.go new file mode 100644 index 0000000..28f91fe --- /dev/null +++ b/app/module/user_role_accesses/service/user_role_accesses.service.go @@ -0,0 +1,80 @@ +package service + +import ( + "campaign-pool-be/app/module/user_role_accesses/mapper" + "campaign-pool-be/app/module/user_role_accesses/repository" + "campaign-pool-be/app/module/user_role_accesses/request" + "campaign-pool-be/app/module/user_role_accesses/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// UserRoleAccessesService +type userRoleAccessesService struct { + Repo repository.UserRoleAccessesRepository + Log zerolog.Logger +} + +// UserRoleAccessesService define interface of IUserRoleAccessesService +type UserRoleAccessesService interface { + All(req request.UserRoleAccessesQueryRequest) (userRoleAccesses []*response.UserRoleAccessesResponse, paging paginator.Pagination, err error) + Show(id uint) (userRoleAccesses *response.UserRoleAccessesResponse, err error) + Save(req request.UserRoleAccessesCreateRequest) (err error) + Update(id uint, req request.UserRoleAccessesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewUserRoleAccessesService init UserRoleAccessesService +func NewUserRoleAccessesService(repo repository.UserRoleAccessesRepository, log zerolog.Logger) UserRoleAccessesService { + + return &userRoleAccessesService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of UserRoleAccessesService +func (_i *userRoleAccessesService) All(req request.UserRoleAccessesQueryRequest) (userRoleAccessess []*response.UserRoleAccessesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + userRoleAccessess = append(userRoleAccessess, mapper.UserRoleAccessesResponseMapper(result)) + } + + return +} + +func (_i *userRoleAccessesService) Show(id uint) (userRoleAccesses *response.UserRoleAccessesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserRoleAccessesResponseMapper(result), nil +} + +func (_i *userRoleAccessesService) Save(req request.UserRoleAccessesCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *userRoleAccessesService) Update(id uint, req request.UserRoleAccessesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userRoleAccessesService) 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) +} diff --git a/app/module/user_role_accesses/user_role_accesses.module.go b/app/module/user_role_accesses/user_role_accesses.module.go new file mode 100644 index 0000000..b54dbaf --- /dev/null +++ b/app/module/user_role_accesses/user_role_accesses.module.go @@ -0,0 +1,54 @@ +package user_role_accesses + +import ( + "campaign-pool-be/app/module/user_role_accesses/controller" + "campaign-pool-be/app/module/user_role_accesses/repository" + "campaign-pool-be/app/module/user_role_accesses/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of UserRoleAccessesRouter +type UserRoleAccessesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserRoleAccesses module +var NewUserRoleAccessesModule = fx.Options( + // register repository of UserRoleAccesses module + fx.Provide(repository.NewUserRoleAccessesRepository), + + // register service of UserRoleAccesses module + fx.Provide(service.NewUserRoleAccessesService), + + // register controller of UserRoleAccesses module + fx.Provide(controller.NewController), + + // register router of UserRoleAccesses module + fx.Provide(NewUserRoleAccessesRouter), +) + +// init UserRoleAccessesRouter +func NewUserRoleAccessesRouter(fiber *fiber.App, controller *controller.Controller) *UserRoleAccessesRouter { + return &UserRoleAccessesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserRoleAccesses module +func (_i *UserRoleAccessesRouter) RegisterUserRoleAccessesRoutes() { + // define controllers + userRoleAccessesController := _i.Controller.UserRoleAccesses + + // define routes + _i.App.Route("/user-role-accesses", func(router fiber.Router) { + router.Get("/", userRoleAccessesController.All) + router.Get("/:id", userRoleAccessesController.Show) + router.Post("/", userRoleAccessesController.Save) + router.Put("/:id", userRoleAccessesController.Update) + router.Delete("/:id", userRoleAccessesController.Delete) + }) +} diff --git a/app/module/user_role_level_details/controller/controller.go b/app/module/user_role_level_details/controller/controller.go new file mode 100644 index 0000000..2aed410 --- /dev/null +++ b/app/module/user_role_level_details/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/user_role_level_details/service" + +type Controller struct { + UserRoleLevelDetails UserRoleLevelDetailsController +} + +func NewController(UserRoleLevelDetailsService service.UserRoleLevelDetailsService) *Controller { + return &Controller{ + UserRoleLevelDetails: NewUserRoleLevelDetailsController(UserRoleLevelDetailsService), + } +} diff --git a/app/module/user_role_level_details/controller/user_role_level_details.controller.go b/app/module/user_role_level_details/controller/user_role_level_details.controller.go new file mode 100644 index 0000000..becbc08 --- /dev/null +++ b/app/module/user_role_level_details/controller/user_role_level_details.controller.go @@ -0,0 +1,182 @@ +package controller + +import ( + "campaign-pool-be/app/module/user_role_level_details/request" + "campaign-pool-be/app/module/user_role_level_details/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type userRoleLevelDetailsController struct { + userRoleLevelDetailsService service.UserRoleLevelDetailsService +} + +type UserRoleLevelDetailsController 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 NewUserRoleLevelDetailsController(userRoleLevelDetailsService service.UserRoleLevelDetailsService) UserRoleLevelDetailsController { + return &userRoleLevelDetailsController{ + userRoleLevelDetailsService: userRoleLevelDetailsService, + } +} + +// All get all UserRoleLevelDetails +// @Summary Get all UserRoleLevelDetails +// @Description API for getting all UserRoleLevelDetails +// @Tags Task +// @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 /user-role-level-details [get] +func (_i *userRoleLevelDetailsController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + var req request.UserRoleLevelDetailsQueryRequest + req.Pagination = paginate + + userRoleLevelDetailsData, paging, err := _i.userRoleLevelDetailsService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails list successfully retrieved"}, + Data: userRoleLevelDetailsData, + Meta: paging, + }) +} + +// Show get one UserRoleLevelDetails +// @Summary Get one UserRoleLevelDetails +// @Description API for getting one UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Param id path int true "UserRoleLevelDetails 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 /user-role-level-details/{id} [get] +func (_i *userRoleLevelDetailsController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userRoleLevelDetailsData, err := _i.userRoleLevelDetailsService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully retrieved"}, + Data: userRoleLevelDetailsData, + }) +} + +// Save create UserRoleLevelDetails +// @Summary Create UserRoleLevelDetails +// @Description API for create UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Body request.UserRoleLevelDetailsCreateRequest +// @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 /user-role-level-details [post] +func (_i *userRoleLevelDetailsController) Save(c *fiber.Ctx) error { + req := new(request.UserRoleLevelDetailsCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.userRoleLevelDetailsService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully created"}, + }) +} + +// Update update UserRoleLevelDetails +// @Summary update UserRoleLevelDetails +// @Description API for update UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Body request.UserRoleLevelDetailsUpdateRequest +// @Param id path int true "UserRoleLevelDetails 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 /user-role-level-details/{id} [put] +func (_i *userRoleLevelDetailsController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserRoleLevelDetailsUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userRoleLevelDetailsService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully updated"}, + }) +} + +// Delete delete UserRoleLevelDetails +// @Summary delete UserRoleLevelDetails +// @Description API for delete UserRoleLevelDetails +// @Tags Task +// @Security Bearer +// @Param id path int true "UserRoleLevelDetails 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 /user-role-level-details/{id} [delete] +func (_i *userRoleLevelDetailsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userRoleLevelDetailsService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"UserRoleLevelDetails successfully deleted"}, + }) +} diff --git a/app/module/user_role_level_details/mapper/user_role_level_details.mapper.go b/app/module/user_role_level_details/mapper/user_role_level_details.mapper.go new file mode 100644 index 0000000..604ee36 --- /dev/null +++ b/app/module/user_role_level_details/mapper/user_role_level_details.mapper.go @@ -0,0 +1,20 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/user_role_level_details/response" +) + +func UserRoleLevelDetailsResponseMapper(userRoleLevelDetailsReq *entity.UserRoleLevelDetails) (userRoleLevelDetailsRes *res.UserRoleLevelDetailsResponse) { + if userRoleLevelDetailsReq != nil { + userRoleLevelDetailsRes = &res.UserRoleLevelDetailsResponse{ + ID: userRoleLevelDetailsReq.ID, + UserRoleId: userRoleLevelDetailsReq.UserRoleId, + UserLevelId: userRoleLevelDetailsReq.UserLevelId, + IsActive: userRoleLevelDetailsReq.IsActive, + CreatedAt: userRoleLevelDetailsReq.CreatedAt, + UpdatedAt: userRoleLevelDetailsReq.UpdatedAt, + } + } + return userRoleLevelDetailsRes +} diff --git a/app/module/user_role_level_details/repository/user_role_level_details.repository.go b/app/module/user_role_level_details/repository/user_role_level_details.repository.go new file mode 100644 index 0000000..9c047ba --- /dev/null +++ b/app/module/user_role_level_details/repository/user_role_level_details.repository.go @@ -0,0 +1,83 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/user_role_level_details/request" + "campaign-pool-be/utils/paginator" +) + +type userRoleLevelDetailsRepository struct { + DB *database.Database +} + +// UserRoleLevelDetailsRepository define interface of IUserRoleLevelDetailsRepository +type UserRoleLevelDetailsRepository interface { + GetAll(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetailss []*entity.UserRoleLevelDetails, paging paginator.Pagination, err error) + FindOne(id uint) (userRoleLevelDetails *entity.UserRoleLevelDetails, err error) + FindByUserLevels(userLevelId uint) (userRoleLevelDetails []*entity.UserRoleLevelDetails, err error) + Create(userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) + CreateAll(userRoleLevelDetails *[]entity.UserRoleLevelDetails) (err error) + Update(id uint, userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) + Delete(id uint) (err error) +} + +func NewUserRoleLevelDetailsRepository(db *database.Database) UserRoleLevelDetailsRepository { + return &userRoleLevelDetailsRepository{ + DB: db, + } +} + +// implement interface of IUserRoleLevelDetailsRepository +func (_i *userRoleLevelDetailsRepository) GetAll(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetailss []*entity.UserRoleLevelDetails, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&entity.UserRoleLevelDetails{}) + query.Count(&count) + + req.Pagination.Count = count + req.Pagination = paginator.Paging(req.Pagination) + + err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&userRoleLevelDetailss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userRoleLevelDetailsRepository) FindOne(id uint) (userRoleLevelDetails *entity.UserRoleLevelDetails, err error) { + if err := _i.DB.DB.First(&userRoleLevelDetails, id).Error; err != nil { + return nil, err + } + + return userRoleLevelDetails, nil +} + +func (_i *userRoleLevelDetailsRepository) FindByUserLevels(userLevelId uint) (userRoleLevelDetails []*entity.UserRoleLevelDetails, err error) { + if err := _i.DB.DB.Where(&entity.UserRoleLevelDetails{UserLevelId: userLevelId}).Find(&userRoleLevelDetails).Error; err != nil { + return nil, err + } + + return userRoleLevelDetails, nil +} + +func (_i *userRoleLevelDetailsRepository) Create(userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) { + return _i.DB.DB.Create(userRoleLevelDetails).Error +} + +func (_i *userRoleLevelDetailsRepository) CreateAll(userRoleLevelDetails *[]entity.UserRoleLevelDetails) (err error) { + return _i.DB.DB.Create(userRoleLevelDetails).Error +} + +func (_i *userRoleLevelDetailsRepository) Update(id uint, userRoleLevelDetails *entity.UserRoleLevelDetails) (err error) { + return _i.DB.DB.Model(&entity.UserRoleLevelDetails{}). + Where(&entity.UserRoleLevelDetails{ID: id}). + Updates(userRoleLevelDetails).Error +} + +func (_i *userRoleLevelDetailsRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserRoleLevelDetails{}, id).Error +} diff --git a/app/module/user_role_level_details/request/user_role_level_details.request.go b/app/module/user_role_level_details/request/user_role_level_details.request.go new file mode 100644 index 0000000..3a8e628 --- /dev/null +++ b/app/module/user_role_level_details/request/user_role_level_details.request.go @@ -0,0 +1,51 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "time" +) + +type UserRoleLevelDetailsGeneric interface { + ToEntity() +} + +type UserRoleLevelDetailsQueryRequest struct { + UserRoleId uint `json:"userRoleId" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserRoleLevelDetailsCreateRequest struct { + UserRoleId uint `json:"userRoleId" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + IsActive *bool `json:"isActive" validate:"required"` +} + +func (req UserRoleLevelDetailsCreateRequest) ToEntity() *entity.UserRoleLevelDetails { + return &entity.UserRoleLevelDetails{ + UserRoleId: req.UserRoleId, + UserLevelId: req.UserLevelId, + IsActive: req.IsActive, + } +} + +type UserRoleLevelDetailsUpdateRequest struct { + ID uint `json:"id" validate:"required"` + UserRoleId uint `json:"userRoleId" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + IsActive *bool `json:"isActive" validate:"required"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (req UserRoleLevelDetailsUpdateRequest) ToEntity() *entity.UserRoleLevelDetails { + return &entity.UserRoleLevelDetails{ + ID: req.ID, + UserRoleId: req.UserRoleId, + UserLevelId: req.UserLevelId, + IsActive: req.IsActive, + CreatedAt: req.CreatedAt, + UpdatedAt: req.UpdatedAt, + } +} diff --git a/app/module/user_role_level_details/response/user_role_level_details.response.go b/app/module/user_role_level_details/response/user_role_level_details.response.go new file mode 100644 index 0000000..ae7a1d0 --- /dev/null +++ b/app/module/user_role_level_details/response/user_role_level_details.response.go @@ -0,0 +1,12 @@ +package response + +import "time" + +type UserRoleLevelDetailsResponse struct { + ID uint `json:"id"` + UserRoleId uint `json:"userRoleId"` + UserLevelId uint `json:"userLevelId"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/user_role_level_details/service/user_role_level_details.service.go b/app/module/user_role_level_details/service/user_role_level_details.service.go new file mode 100644 index 0000000..83ac63f --- /dev/null +++ b/app/module/user_role_level_details/service/user_role_level_details.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "campaign-pool-be/app/module/user_role_level_details/mapper" + "campaign-pool-be/app/module/user_role_level_details/repository" + "campaign-pool-be/app/module/user_role_level_details/request" + "campaign-pool-be/app/module/user_role_level_details/response" + "campaign-pool-be/utils/paginator" + + "github.com/rs/zerolog" +) + +// UserRoleLevelDetailsService +type userRoleLevelDetailsService struct { + Repo repository.UserRoleLevelDetailsRepository + Log zerolog.Logger +} + +// UserRoleLevelDetailsService define interface of IUserRoleLevelDetailsService +type UserRoleLevelDetailsService interface { + All(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetails []*response.UserRoleLevelDetailsResponse, paging paginator.Pagination, err error) + Show(id uint) (userRoleLevelDetails *response.UserRoleLevelDetailsResponse, err error) + Save(req request.UserRoleLevelDetailsCreateRequest) (err error) + Update(id uint, req request.UserRoleLevelDetailsUpdateRequest) (err error) + Delete(id uint) error +} + +// NewUserRoleLevelDetailsService init UserRoleLevelDetailsService +func NewUserRoleLevelDetailsService(repo repository.UserRoleLevelDetailsRepository, log zerolog.Logger) UserRoleLevelDetailsService { + + return &userRoleLevelDetailsService{ + Repo: repo, + Log: log, + } +} + +// All implement interface of UserRoleLevelDetailsService +func (_i *userRoleLevelDetailsService) All(req request.UserRoleLevelDetailsQueryRequest) (userRoleLevelDetailss []*response.UserRoleLevelDetailsResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + userRoleLevelDetailss = append(userRoleLevelDetailss, mapper.UserRoleLevelDetailsResponseMapper(result)) + } + + return +} + +func (_i *userRoleLevelDetailsService) Show(id uint) (userRoleLevelDetails *response.UserRoleLevelDetailsResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserRoleLevelDetailsResponseMapper(result), nil +} + +func (_i *userRoleLevelDetailsService) Save(req request.UserRoleLevelDetailsCreateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + return _i.Repo.Create(req.ToEntity()) +} + +func (_i *userRoleLevelDetailsService) Update(id uint, req request.UserRoleLevelDetailsUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userRoleLevelDetailsService) Delete(id uint) error { + return _i.Repo.Delete(id) +} diff --git a/app/module/user_role_level_details/user_role_level_details.module.go b/app/module/user_role_level_details/user_role_level_details.module.go new file mode 100644 index 0000000..5db41f2 --- /dev/null +++ b/app/module/user_role_level_details/user_role_level_details.module.go @@ -0,0 +1,54 @@ +package user_role_level_details + +import ( + "campaign-pool-be/app/module/user_role_level_details/controller" + "campaign-pool-be/app/module/user_role_level_details/repository" + "campaign-pool-be/app/module/user_role_level_details/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of UserRoleLevelDetailsRouter +type UserRoleLevelDetailsRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserRoleLevelDetails module +var NewUserRoleLevelDetailsModule = fx.Options( + // register repository of UserRoleLevelDetails module + fx.Provide(repository.NewUserRoleLevelDetailsRepository), + + // register service of UserRoleLevelDetails module + fx.Provide(service.NewUserRoleLevelDetailsService), + + // register controller of UserRoleLevelDetails module + fx.Provide(controller.NewController), + + // register router of UserRoleLevelDetails module + fx.Provide(NewUserRoleLevelDetailsRouter), +) + +// init UserRoleLevelDetailsRouter +func NewUserRoleLevelDetailsRouter(fiber *fiber.App, controller *controller.Controller) *UserRoleLevelDetailsRouter { + return &UserRoleLevelDetailsRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserRoleLevelDetails module +func (_i *UserRoleLevelDetailsRouter) RegisterUserRoleLevelDetailsRoutes() { + // define controllers + userRoleLevelDetailsController := _i.Controller.UserRoleLevelDetails + + // define routes + _i.App.Route("/user-role-level-details", func(router fiber.Router) { + router.Get("/", userRoleLevelDetailsController.All) + router.Get("/:id", userRoleLevelDetailsController.Show) + router.Post("/", userRoleLevelDetailsController.Save) + router.Put("/:id", userRoleLevelDetailsController.Update) + router.Delete("/:id", userRoleLevelDetailsController.Delete) + }) +} diff --git a/app/module/user_roles/controller/controller.go b/app/module/user_roles/controller/controller.go new file mode 100644 index 0000000..f713423 --- /dev/null +++ b/app/module/user_roles/controller/controller.go @@ -0,0 +1,17 @@ +package controller + +import ( + "campaign-pool-be/app/module/user_roles/service" + + "github.com/rs/zerolog" +) + +type Controller struct { + UserRoles UserRolesController +} + +func NewController(UserRolesService service.UserRolesService, log zerolog.Logger) *Controller { + return &Controller{ + UserRoles: NewUserRolesController(UserRolesService, log), + } +} diff --git a/app/module/user_roles/controller/user_roles.controller.go b/app/module/user_roles/controller/user_roles.controller.go new file mode 100644 index 0000000..7a917f4 --- /dev/null +++ b/app/module/user_roles/controller/user_roles.controller.go @@ -0,0 +1,199 @@ +package controller + +import ( + "campaign-pool-be/app/module/user_roles/request" + "campaign-pool-be/app/module/user_roles/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog" +) + +type userRolesController struct { + userRolesService service.UserRolesService + Log zerolog.Logger +} + +type UserRolesController 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 NewUserRolesController(userRolesService service.UserRolesService, log zerolog.Logger) UserRolesController { + return &userRolesController{ + userRolesService: userRolesService, + Log: log, + } +} + +// All UserRoles +// @Summary Get all UserRoles +// @Description API for getting all UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param req query request.UserRolesQueryRequest 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 /user-roles [get] +func (_i *userRolesController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UserRolesQueryRequestContext{ + Name: c.Query("name"), + Description: c.Query("description"), + Code: c.Query("code"), + UserLevelId: c.Query("userLevelId"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + userRolesData, paging, err := _i.userRolesService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles list successfully retrieved"}, + Data: userRolesData, + Meta: paging, + }) +} + +// Show UserRoles +// @Summary Get one UserRoles +// @Description API for getting one UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param id path int true "UserRoles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles/{id} [get] +func (_i *userRolesController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userRolesData, err := _i.userRolesService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully retrieved"}, + Data: userRolesData, + }) +} + +// Save UserRoles +// @Summary Create UserRoles +// @Description API for create UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserRolesCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles [post] +func (_i *userRolesController) Save(c *fiber.Ctx) error { + req := new(request.UserRolesCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + err := _i.userRolesService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully created"}, + }) +} + +// Update UserRoles +// @Summary update UserRoles +// @Description API for update UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserRolesUpdateRequest true "Required payload" +// @Param id path int true "UserRoles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles/{id} [put] +func (_i *userRolesController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UserRolesUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.userRolesService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully updated"}, + }) +} + +// Delete UserRoles +// @Summary delete UserRoles +// @Description API for delete UserRoles +// @Tags UserRoles +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "UserRoles ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /user-roles/{id} [delete] +func (_i *userRolesController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.userRolesService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"UserRoles successfully deleted"}, + }) +} diff --git a/app/module/user_roles/mapper/user_roles.mapper.go b/app/module/user_roles/mapper/user_roles.mapper.go new file mode 100644 index 0000000..9575926 --- /dev/null +++ b/app/module/user_roles/mapper/user_roles.mapper.go @@ -0,0 +1,24 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + res "campaign-pool-be/app/module/user_roles/response" +) + +func UserRolesResponseMapper(userRolesReq *entity.UserRoles) (userRolesRes *res.UserRolesResponse) { + if userRolesReq != nil { + userRolesRes = &res.UserRolesResponse{ + ID: userRolesReq.ID, + Name: userRolesReq.Name, + Description: userRolesReq.Description, + Code: userRolesReq.Code, + UserLevelId: userRolesReq.UserLevelId, + StatusId: userRolesReq.StatusId, + CreatedById: userRolesReq.CreatedById, + IsActive: userRolesReq.IsActive, + CreatedAt: userRolesReq.CreatedAt, + UpdatedAt: userRolesReq.UpdatedAt, + } + } + return userRolesRes +} diff --git a/app/module/user_roles/repository/user_roles.repository.go b/app/module/user_roles/repository/user_roles.repository.go new file mode 100644 index 0000000..6a262dd --- /dev/null +++ b/app/module/user_roles/repository/user_roles.repository.go @@ -0,0 +1,106 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/user_roles/request" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type userRolesRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// UserRolesRepository define interface of IUserRolesRepository +type UserRolesRepository interface { + GetAll(req request.UserRolesQueryRequest) (userRoles []*entity.UserRoles, paging paginator.Pagination, err error) + FindOne(id uint) (userRoles *entity.UserRoles, err error) + Create(userRoles *entity.UserRoles) (userRolesReturn *entity.UserRoles, err error) + Update(id uint, userRoles *entity.UserRoles) (err error) + Delete(id uint) (err error) +} + +func NewUserRolesRepository(db *database.Database, log zerolog.Logger) UserRolesRepository { + return &userRolesRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IUserRolesRepository +func (_i *userRolesRepository) GetAll(req request.UserRolesQueryRequest) (userRoles []*entity.UserRoles, paging paginator.Pagination, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var count int64 + + query := _i.DB.DB.Model(&entity.UserRoles{}) + query = query.Where("is_active = ?", true) + + if req.Name != nil && *req.Name != "" { + name := strings.ToLower(*req.Name) + query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(name)+"%") + } + if req.Code != nil { + query = query.Where("code = ?", req.Code) + } + if req.UserLevelId != nil { + query = query.Where("user_level_id = ?", req.UserLevelId) + } + 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(&userRoles).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *userRolesRepository) FindOne(id uint) (userRoles *entity.UserRoles, err error) { + if err := _i.DB.DB.First(&userRoles, id).Error; err != nil { + return nil, err + } + + return userRoles, nil +} + +func (_i *userRolesRepository) Create(userRoles *entity.UserRoles) (userRolesReturn *entity.UserRoles, err error) { + result := _i.DB.DB.Create(userRoles) + return userRoles, result.Error +} + +func (_i *userRolesRepository) Update(id uint, userRoles *entity.UserRoles) (err error) { + userRolesMap, err := utilSvc.StructToMap(userRoles) + if err != nil { + return err + } + return _i.DB.DB.Model(&entity.UserRoles{}). + Where(&entity.UserRoles{ID: id}). + Updates(userRolesMap).Error +} + +func (_i *userRolesRepository) Delete(id uint) error { + return _i.DB.DB.Delete(&entity.UserRoles{}, id).Error +} diff --git a/app/module/user_roles/request/user_roles.request.go b/app/module/user_roles/request/user_roles.request.go new file mode 100644 index 0000000..5f57b8c --- /dev/null +++ b/app/module/user_roles/request/user_roles.request.go @@ -0,0 +1,92 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + userRoleAccessReq "campaign-pool-be/app/module/user_role_accesses/request" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type UserRolesGeneric interface { + ToEntity() +} + +type UserRolesQueryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + Code *string `json:"code"` + UserLevelId *int `json:"userLevelId"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UserRolesCreateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + Code string `json:"code" validate:"required"` + UserLevelIds []uint `json:"userLevelIds" validate:"required"` + UserRoleAccess []userRoleAccessReq.UserRoleAccessesCreateRequest `json:"userRoleAccess" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req UserRolesCreateRequest) ToEntity() *entity.UserRoles { + return &entity.UserRoles{ + Name: req.Name, + Description: req.Description, + Code: req.Code, + StatusId: req.StatusId, + } +} + +type UserRolesUpdateRequest struct { + Name string `json:"name" validate:"required"` + Description string `json:"description" validate:"required"` + Code string `json:"code" validate:"required"` + LevelNumber int `json:"levelNumber" validate:"required"` + UserLevelIds []uint `json:"userLevelIds" validate:"required"` + StatusId int `json:"statusId" validate:"required"` +} + +func (req UserRolesUpdateRequest) ToEntity() *entity.UserRoles { + return &entity.UserRoles{ + Name: req.Name, + Description: req.Description, + Code: req.Code, + StatusId: req.StatusId, + UpdatedAt: time.Now(), + } +} + +type UserRolesQueryRequestContext struct { + Name string `json:"name"` + Description string `json:"description"` + Code string `json:"code"` + UserLevelId string `json:"userLevelId"` + StatusId string `json:"statusId"` +} + +func (req UserRolesQueryRequestContext) ToParamRequest() UserRolesQueryRequest { + var request UserRolesQueryRequest + + if name := req.Name; name != "" { + request.Name = &name + } + if code := req.Code; code != "" { + request.Code = &code + } + if userLevelIdStr := req.UserLevelId; userLevelIdStr != "" { + userLevelId, err := strconv.Atoi(userLevelIdStr) + if err == nil { + request.UserLevelId = &userLevelId + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/user_roles/response/user_roles.response.go b/app/module/user_roles/response/user_roles.response.go new file mode 100644 index 0000000..967c79d --- /dev/null +++ b/app/module/user_roles/response/user_roles.response.go @@ -0,0 +1,16 @@ +package response + +import "time" + +type UserRolesResponse struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Code string `json:"code"` + UserLevelId uint `json:"userLevelId"` + StatusId int `json:"statusId"` + CreatedById *uint `json:"createdById"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/app/module/user_roles/service/user_roles.service.go b/app/module/user_roles/service/user_roles.service.go new file mode 100644 index 0000000..93de953 --- /dev/null +++ b/app/module/user_roles/service/user_roles.service.go @@ -0,0 +1,143 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + userLevelsRepository "campaign-pool-be/app/module/user_levels/repository" + userRoleAccessRepository "campaign-pool-be/app/module/user_role_accesses/repository" + userRoleLevelDetailsRepository "campaign-pool-be/app/module/user_role_level_details/repository" + "campaign-pool-be/app/module/user_roles/mapper" + "campaign-pool-be/app/module/user_roles/repository" + "campaign-pool-be/app/module/user_roles/request" + "campaign-pool-be/app/module/user_roles/response" + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + + "github.com/rs/zerolog" +) + +// UserRolesService +type userRolesService struct { + Repo repository.UserRolesRepository + UsersRepo usersRepository.UsersRepository + UserLevelsRepo userLevelsRepository.UserLevelsRepository + UserRoleLevelDetailsRepo userRoleLevelDetailsRepository.UserRoleLevelDetailsRepository + UserRoleAccessRepo userRoleAccessRepository.UserRoleAccessesRepository + Log zerolog.Logger +} + +// UserRolesService define interface of IUserRolesService +type UserRolesService interface { + All(req request.UserRolesQueryRequest) (userRoles []*response.UserRolesResponse, paging paginator.Pagination, err error) + Show(id uint) (userRoles *response.UserRolesResponse, err error) + Save(req request.UserRolesCreateRequest, authToken string) (err error) + Update(id uint, req request.UserRolesUpdateRequest) (err error) + Delete(id uint) error +} + +// NewUserRolesService init UserRolesService +func NewUserRolesService( + repo repository.UserRolesRepository, + usersRepo usersRepository.UsersRepository, + userLevelsRepo userLevelsRepository.UserLevelsRepository, + userRoleLevelDetailsRepo userRoleLevelDetailsRepository.UserRoleLevelDetailsRepository, + userRoleAccessRepo userRoleAccessRepository.UserRoleAccessesRepository, + log zerolog.Logger, +) UserRolesService { + + return &userRolesService{ + Repo: repo, + UsersRepo: usersRepo, + UserLevelsRepo: userLevelsRepo, + UserRoleLevelDetailsRepo: userRoleLevelDetailsRepo, + UserRoleAccessRepo: userRoleAccessRepo, + Log: log, + } +} + +// All implement interface of UserRolesService +func (_i *userRolesService) All(req request.UserRolesQueryRequest) (userRoless []*response.UserRolesResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + + if err != nil { + return + } + + for _, result := range results { + userRoless = append(userRoless, mapper.UserRolesResponseMapper(result)) + } + + return +} + +func (_i *userRolesService) Show(id uint) (userRoles *response.UserRolesResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UserRolesResponseMapper(result), nil +} + +func (_i *userRolesService) Save(req request.UserRolesCreateRequest, authToken string) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken) + newReq.CreatedById = &createdBy.ID + + userRolesReturn, err := _i.Repo.Create(newReq) + if err == nil { + var userRoleAccessList []entity.UserRoleAccesses + for _, item := range req.UserRoleAccess { + userRoleAccess := entity.UserRoleAccesses{ + UserRoleId: userRolesReturn.ID, + MenuId: item.MenuId, + IsViewEnabled: item.IsViewEnabled, + IsInsertEnabled: item.IsInsertEnabled, + IsUpdateEnabled: item.IsUpdateEnabled, + IsDeleteEnabled: item.IsDeleteEnabled, + IsApprovalEnabled: item.IsApprovalEnabled, + IsAdminEnabled: item.IsAdminEnabled, + } + userRoleAccessList = append(userRoleAccessList, userRoleAccess) + } + err := _i.UserRoleAccessRepo.CreateAll(&userRoleAccessList) + if err != nil { + return err + } + + var UserRoleLevelDetailList []entity.UserRoleLevelDetails + isActive := true + for _, id := range req.UserLevelIds { + userRoleLevelDetail := entity.UserRoleLevelDetails{ + UserRoleId: userRolesReturn.ID, + UserLevelId: id, + IsActive: &isActive, + } + UserRoleLevelDetailList = append(UserRoleLevelDetailList, userRoleLevelDetail) + } + err = _i.UserRoleLevelDetailsRepo.CreateAll(&UserRoleLevelDetailList) + if err != nil { + return err + } + } + + return err +} + +func (_i *userRolesService) Update(id uint, req request.UserRolesUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + return _i.Repo.Update(id, req.ToEntity()) +} + +func (_i *userRolesService) Delete(id uint) (err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + isActive := false + result.IsActive = &isActive + return _i.Repo.Update(id, result) +} diff --git a/app/module/user_roles/user_roles.module.go b/app/module/user_roles/user_roles.module.go new file mode 100644 index 0000000..6f3bbb6 --- /dev/null +++ b/app/module/user_roles/user_roles.module.go @@ -0,0 +1,54 @@ +package user_roles + +import ( + "campaign-pool-be/app/module/user_roles/controller" + "campaign-pool-be/app/module/user_roles/repository" + "campaign-pool-be/app/module/user_roles/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of UserRolesRouter +type UserRolesRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of UserRoles module +var NewUserRolesModule = fx.Options( + // register repository of UserRoles module + fx.Provide(repository.NewUserRolesRepository), + + // register service of UserRoles module + fx.Provide(service.NewUserRolesService), + + // register controller of UserRoles module + fx.Provide(controller.NewController), + + // register router of UserRoles module + fx.Provide(NewUserRolesRouter), +) + +// init UserRolesRouter +func NewUserRolesRouter(fiber *fiber.App, controller *controller.Controller) *UserRolesRouter { + return &UserRolesRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of UserRoles module +func (_i *UserRolesRouter) RegisterUserRolesRoutes() { + // define controllers + userRolesController := _i.Controller.UserRoles + + // define routes + _i.App.Route("/user-roles", func(router fiber.Router) { + router.Get("/", userRolesController.All) + router.Get("/:id", userRolesController.Show) + router.Post("/", userRolesController.Save) + router.Put("/:id", userRolesController.Update) + router.Delete("/:id", userRolesController.Delete) + }) +} diff --git a/app/module/users/controller/controller.go b/app/module/users/controller/controller.go new file mode 100644 index 0000000..cc7a28a --- /dev/null +++ b/app/module/users/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/users/service" + +type Controller struct { + Users UsersController +} + +func NewController(UsersService service.UsersService) *Controller { + return &Controller{ + Users: NewUsersController(UsersService), + } +} diff --git a/app/module/users/controller/users.controller.go b/app/module/users/controller/users.controller.go new file mode 100644 index 0000000..9f6f0fc --- /dev/null +++ b/app/module/users/controller/users.controller.go @@ -0,0 +1,546 @@ +package controller + +import ( + "campaign-pool-be/app/module/users/request" + "campaign-pool-be/app/module/users/service" + "campaign-pool-be/utils/paginator" + "strconv" + + "github.com/gofiber/fiber/v2" + _ "github.com/gofiber/fiber/v2" + + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" +) + +type usersController struct { + usersService service.UsersService +} + +type UsersController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error + ShowByUsername(c *fiber.Ctx) error + ShowInfo(c *fiber.Ctx) error + Save(c *fiber.Ctx) error + Update(c *fiber.Ctx) error + Login(c *fiber.Ctx) error + ParetoLogin(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error + SavePassword(c *fiber.Ctx) error + ResetPassword(c *fiber.Ctx) error + ForgotPassword(c *fiber.Ctx) error + OtpRequest(c *fiber.Ctx) error + OtpValidation(c *fiber.Ctx) error + EmailValidation(c *fiber.Ctx) error + SetupEmail(c *fiber.Ctx) error +} + +func NewUsersController(usersService service.UsersService) UsersController { + return &usersController{ + usersService: usersService, + } +} + +// All Users +// @Summary Get all Users +// @Description API for getting all Users +// @Tags Users +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param req query request.UsersQueryRequest 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 /users [get] +func (_i *usersController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + + reqContext := request.UsersQueryRequestContext{ + Username: c.Query("username"), + Email: c.Query("email"), + Fullname: c.Query("fullname"), + PhoneNumber: c.Query("phoneNumber"), + WorkType: c.Query("workType"), + GenderType: c.Query("genderType"), + IdentityType: c.Query("identityType"), + IdentityGroup: c.Query("identityGroup"), + IdentityGroupNumber: c.Query("identityGroupNumber"), + IdentityNumber: c.Query("identityNumber"), + UserRoleId: c.Query("userRoleId"), + StatusId: c.Query("statusId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + usersData, paging, err := _i.usersService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users list successfully retrieved"}, + Data: usersData, + Meta: paging, + }) +} + +// Show Users +// @Summary Get one Users +// @Description API for getting one Users +// @Tags Users +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Users ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/detail/{id} [get] +func (_i *usersController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + usersData, err := _i.usersService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully retrieved"}, + Data: usersData, + }) +} + +// ShowByUsername Users +// @Summary Get one Users +// @Description API for getting one Users +// @Tags Users +// @Security Bearer +// @Param username path string true "Username" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/username/{username} [get] +func (_i *usersController) ShowByUsername(c *fiber.Ctx) error { + username := c.Params("username") + + usersData, err := _i.usersService.ShowByUsername(username) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully retrieved"}, + Data: usersData, + }) +} + +// ShowInfo Users +// @Summary ShowInfo Users +// @Description API for ShowUserInfo +// @Tags Users +// @Security Bearer +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/info [get] +func (_i *usersController) ShowInfo(c *fiber.Ctx) error { + authToken := c.Get("Authorization") + + dataResult, err := _i.usersService.ShowUserInfo(authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully retrieve"}, + Data: dataResult, + }) +} + +// Save Users +// @Summary Create Users +// @Description API for create Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UsersCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users [post] +func (_i *usersController) Save(c *fiber.Ctx) error { + req := new(request.UsersCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + dataResult, err := _i.usersService.Save(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully created"}, + Data: dataResult, + }) +} + +// Update Users +// @Summary update Users +// @Description API for update Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param id path int true "Users ID" +// @Param payload body request.UsersUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/{id} [put] +func (_i *usersController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + req := new(request.UsersUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.usersService.Update(uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully updated"}, + }) +} + +// Login Users +// @Summary Login Users +// @Description API for Login Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserLogin true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/login [post] +func (_i *usersController) Login(c *fiber.Ctx) error { + req := new(request.UserLogin) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + loginResponse, err := _i.usersService.Login(*req) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Code: 401, + Messages: utilRes.Messages{err.Error()}, + }) + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully login"}, + Data: loginResponse, + }) +} + +// ParetoLogin Users +// @Summary ParetoLogin Users +// @Description API for ParetoLogin Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserLogin true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/pareto-login [post] +func (_i *usersController) ParetoLogin(c *fiber.Ctx) error { + req := new(request.UserLogin) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + loginResponse, err := _i.usersService.ParetoLogin(*req) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Code: 401, + Messages: utilRes.Messages{err.Error()}, + }) + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully login"}, + Data: loginResponse, + }) +} + +// Delete Users +// @Summary delete Users +// @Description API for delete Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Users ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/{id} [delete] +func (_i *usersController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.usersService.Delete(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users successfully deleted"}, + }) +} + +// SavePassword Users +// @Summary SavePassword Users +// @Description API for SavePassword Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param Authorization header string false "Insert your access token" default(Bearer ) +// @Param payload body request.UserSavePassword true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/save-password [post] +func (_i *usersController) SavePassword(c *fiber.Ctx) error { + req := new(request.UserSavePassword) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + authToken := c.Get("Authorization") + + err := _i.usersService.SavePassword(*req, authToken) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users password successfully update"}, + }) +} + +// ResetPassword Users +// @Summary ResetPassword Users +// @Description API for ResetPassword Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserResetPassword true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/reset-password [post] +func (_i *usersController) ResetPassword(c *fiber.Ctx) error { + req := new(request.UserResetPassword) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.ResetPassword(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users password successfully reset"}, + }) +} + +// ForgotPassword Users +// @Summary ForgotPassword Users +// @Description API for ForgotPassword Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserForgotPassword true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/forgot-password [post] +func (_i *usersController) ForgotPassword(c *fiber.Ctx) error { + req := new(request.UserForgotPassword) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.ForgotPassword(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Users forgot password has sent"}, + }) +} + +// OtpRequest Users +// @Summary OtpRequest Users +// @Description API for OtpRequest Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserOtpRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/otp-request [post] +func (_i *usersController) OtpRequest(c *fiber.Ctx) error { + req := new(request.UserOtpRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.OtpRequest(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Otp has sent"}, + }) +} + +// OtpValidation Users +// @Summary OtpValidation Users +// @Description API for OtpValidation Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserOtpValidation true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/otp-validation [post] +func (_i *usersController) OtpValidation(c *fiber.Ctx) error { + req := new(request.UserOtpValidation) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err := _i.usersService.OtpValidation(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"OTP is valid"}, + }) +} + +// EmailValidation Users +// @Summary EmailValidation Users +// @Description API for Email Validation Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserEmailValidationRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/email-validation [post] +func (_i *usersController) EmailValidation(c *fiber.Ctx) error { + req := new(request.UserEmailValidationRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + messageResponse, err := _i.usersService.EmailValidationPreLogin(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{messageResponse}, + }) +} + +// SetupEmail Users +// @Summary SetupEmail Users +// @Description API for Setup Email Users +// @Tags Users +// @Security Bearer +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param payload body request.UserEmailValidationRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /users/setup-email [post] +func (_i *usersController) SetupEmail(c *fiber.Ctx) error { + req := new(request.UserEmailValidationRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + messageResponse, err := _i.usersService.SetupEmail(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{messageResponse}, + }) +} diff --git a/app/module/users/mapper/users.mapper.go b/app/module/users/mapper/users.mapper.go new file mode 100644 index 0000000..f94e705 --- /dev/null +++ b/app/module/users/mapper/users.mapper.go @@ -0,0 +1,45 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity/users" + userLevelsRepository "campaign-pool-be/app/module/user_levels/repository" + res "campaign-pool-be/app/module/users/response" +) + +func UsersResponseMapper(usersReq *users.Users, userLevelsRepo userLevelsRepository.UserLevelsRepository) (usersRes *res.UsersResponse) { + if usersReq != nil { + findUserLevel, _ := userLevelsRepo.FindOne(usersReq.UserLevelId) + userLevelGroup := "" + if findUserLevel != nil { + userLevelGroup = findUserLevel.AliasName + } + + usersRes = &res.UsersResponse{ + ID: usersReq.ID, + Username: usersReq.Username, + Email: usersReq.Email, + Fullname: usersReq.Fullname, + Address: usersReq.Address, + PhoneNumber: usersReq.PhoneNumber, + WhatsappNumber: usersReq.WhatsappNumber, + Degree: usersReq.Degree, + WorkType: usersReq.WorkType, + GenderType: usersReq.GenderType, + IdentityType: usersReq.IdentityType, + IdentityNumber: usersReq.IdentityNumber, + DateOfBirth: usersReq.DateOfBirth, + LastEducation: usersReq.LastEducation, + KeycloakId: usersReq.KeycloakId, + UserRoleId: usersReq.UserRoleId, + UserLevelGroup: userLevelGroup, + StatusId: usersReq.StatusId, + UserLevelId: usersReq.UserLevelId, + CreatedById: usersReq.CreatedById, + ProfilePicturePath: usersReq.ProfilePicturePath, + IsActive: usersReq.IsActive, + CreatedAt: usersReq.CreatedAt, + UpdatedAt: usersReq.UpdatedAt, + } + } + return usersRes +} diff --git a/app/module/users/repository/users.repository.go b/app/module/users/repository/users.repository.go new file mode 100644 index 0000000..b4d18cc --- /dev/null +++ b/app/module/users/repository/users.repository.go @@ -0,0 +1,220 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/database/entity/users" + "campaign-pool-be/app/module/users/request" + "campaign-pool-be/utils/paginator" + "encoding/json" + "fmt" + "strings" + + "github.com/rs/zerolog" +) + +type usersRepository struct { + DB *database.Database + Log zerolog.Logger +} + +// UsersRepository define interface of IUsersRepository +type UsersRepository interface { + GetAll(req request.UsersQueryRequest) (userss []*users.Users, paging paginator.Pagination, err error) + FindOne(id uint) (users *users.Users, err error) + FindByKeycloakId(keycloakId string) (users *users.Users, err error) + FindByUsername(username string) (users *users.Users, err error) + Create(users *users.Users) (userReturn *users.Users, err error) + Update(id uint, users *users.Users) (err error) + Delete(id uint) (err error) + CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) + UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error) + FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error) + CreateOtp(otp *entity.OneTimePasswords) (err error) + FindOtpByEmail(email string, code string) (otp *entity.OneTimePasswords, err error) + FindOtpByIdentity(identity string, code string) (otp *entity.OneTimePasswords, err error) +} + +func NewUsersRepository(db *database.Database, log zerolog.Logger) UsersRepository { + return &usersRepository{ + DB: db, + Log: log, + } +} + +// implement interface of IUsersRepository +func (_i *usersRepository) GetAll(req request.UsersQueryRequest) (userss []*users.Users, paging paginator.Pagination, err error) { + var count int64 + + query := _i.DB.DB.Model(&users.Users{}) + + query = query.Where("is_active = ?", true) + + if req.Username != nil && *req.Username != "" { + username := strings.ToLower(*req.Username) + query = query.Where("LOWER(username) LIKE ?", "%"+strings.ToLower(username)+"%") + } + if req.Fullname != nil && *req.Fullname != "" { + fullname := strings.ToLower(*req.Fullname) + query = query.Where("LOWER(fullname) LIKE ?", "%"+strings.ToLower(fullname)+"%") + } + if req.Email != nil && *req.Email != "" { + query = query.Where("email = ?", req.Email) + } + if req.PhoneNumber != nil && *req.PhoneNumber != "" { + query = query.Where("phone_number = ?", req.PhoneNumber) + } + if req.WorkType != nil && *req.WorkType != "" { + query = query.Where("work_type = ?", req.WorkType) + } + if req.GenderType != nil && *req.GenderType != "" { + query = query.Where("gender_type = ?", req.GenderType) + } + if req.IdentityType != nil && *req.IdentityType != "" { + query = query.Where("identity_type = ?", req.IdentityType) + } + if req.IdentityGroup != nil && *req.IdentityGroup != "" { + query = query.Where("identity_group = ?", req.IdentityGroup) + } + if req.IdentityGroupNumber != nil && *req.IdentityGroupNumber != "" { + query = query.Where("identity_group_number = ?", req.IdentityGroupNumber) + } + if req.IdentityNumber != nil && *req.IdentityNumber != "" { + query = query.Where("identity_number = ?", req.IdentityNumber) + } + if req.UserRoleId != nil { + query = query.Where("user_role_id = ?", req.UserRoleId) + } + 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(&userss).Error + if err != nil { + return + } + + paging = *req.Pagination + + return +} + +func (_i *usersRepository) FindOne(id uint) (users *users.Users, err error) { + query := _i.DB.DB.Where("id = ?", id) + + if err := query.Preload("UserLevel").First(&users).Error; err != nil { + return nil, err + } + + return users, nil +} + +func (_i *usersRepository) FindByKeycloakId(keycloakId string) (users *users.Users, err error) { + query := _i.DB.DB.Where("keycloak_id = ?", keycloakId) + if err := query.Preload("UserLevel").First(&users).Error; err != nil { + return nil, err + } + + return users, nil +} + +func (_i *usersRepository) FindByUsername(username string) (users *users.Users, err error) { + query := _i.DB.DB.Where("username = ?", username) + + if err := query.Preload("UserLevel").First(&users).Error; err != nil { + return nil, err + } + + return users, nil +} + +func (_i *usersRepository) Create(users *users.Users) (userReturn *users.Users, err error) { + result := _i.DB.DB.Create(users) + return users, result.Error +} + +func (_i *usersRepository) Update(id uint, userReturn *users.Users) (err error) { + userReturnMap, err := StructToMap(userReturn) + delete(userReturnMap, "user_levels") + _i.Log.Info().Interface("Update", userReturnMap).Msg("") + if err != nil { + return err + } + query := _i.DB.DB.Model(&users.Users{}).Where(&users.Users{ID: id}) + return query.Updates(userReturnMap).Error +} + +func (_i *usersRepository) Delete(id uint) error { + query := _i.DB.DB.Model(&users.Users{}).Where("id = ?", id) + return query.Delete(&users.Users{}).Error +} + +func (_i *usersRepository) CreateForgotPassword(forgotPasswords *entity.ForgotPasswords) (err error) { + result := _i.DB.DB.Create(forgotPasswords) + return result.Error +} + +func (_i *usersRepository) UpdateForgotPassword(id uint, forgotPasswords *entity.ForgotPasswords) (err error) { + _i.Log.Info().Interface("forgotPasswords", id).Msg("") + _i.Log.Info().Interface("forgotPasswords", forgotPasswords).Msg("") + + return _i.DB.DB.Model(&entity.ForgotPasswords{}). + Where(&entity.ForgotPasswords{ID: id}). + Updates(forgotPasswords).Error +} + +func (_i *usersRepository) FindForgotPassword(keycloakId string, code string) (forgotPasswords *entity.ForgotPasswords, err error) { + if err := _i.DB.DB.Where("keycloak_id = ?", keycloakId).Where("code_request = ?", code).Where("is_active = ?", true).First(&forgotPasswords).Error; err != nil { + return nil, err + } + + return forgotPasswords, nil +} + +func (_i *usersRepository) CreateOtp(otp *entity.OneTimePasswords) (err error) { + result := _i.DB.DB.Create(otp) + return result.Error +} + +func (_i *usersRepository) FindOtpByEmail(email string, code string) (otp *entity.OneTimePasswords, err error) { + if err := _i.DB.DB.Where("email = ?", email).Where("otp_code = ?", code).First(&otp).Error; err != nil { + return nil, err + } + + return otp, nil +} + +func (_i *usersRepository) FindOtpByIdentity(identity string, code string) (otp *entity.OneTimePasswords, err error) { + if err := _i.DB.DB.Where("identity = ?", identity).Where("otp_code = ?", code).First(&otp).Error; err != nil { + return nil, err + } + + return otp, nil +} + +func StructToMap(obj interface{}) (map[string]interface{}, error) { + var result map[string]interface{} + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonData, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/app/module/users/request/users.request.go b/app/module/users/request/users.request.go new file mode 100644 index 0000000..060cd4a --- /dev/null +++ b/app/module/users/request/users.request.go @@ -0,0 +1,229 @@ +package request + +import ( + "campaign-pool-be/app/database/entity/users" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type UsersGeneric interface { + ToEntity() +} + +type UsersQueryRequest struct { + Username *string `json:"username"` + Email *string `json:"email"` + Fullname *string `json:"fullname"` + PhoneNumber *string `json:"phoneNumber"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityGroup *string `json:"identityGroup"` + IdentityGroupNumber *string `json:"identityGroupNumber"` + IdentityNumber *string `json:"identityNumber"` + Degree *string `json:"degree"` + UserRoleId *int `json:"userRoleId"` + StatusId *int `json:"statusId"` + Pagination *paginator.Pagination `json:"pagination"` +} + +type UsersCreateRequest struct { + Username string `json:"username" validate:"required,lowercase"` + Email string `json:"email" validate:"required,email"` + Fullname string `json:"fullname" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + UserRoleId uint `json:"userRoleId" validate:"required"` + Password string `json:"password" validate:"required"` + PhoneNumber *string `json:"phoneNumber"` + Address *string `json:"address"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityGroup *string `json:"identityGroup"` + IdentityGroupNumber *string `json:"identityGroupNumber"` + IdentityNumber *string `json:"identityNumber"` + DateOfBirth *string `json:"dateOfBirth"` + LastEducation *string `json:"lastEducation"` + Degree *string `json:"degree"` + WhatsappNumber *string `json:"whatsappNumber"` + LastJobTitle *string `json:"lastJobTitle"` +} + +func (req UsersCreateRequest) ToEntity() *users.Users { + return &users.Users{ + Username: req.Username, + Email: req.Email, + Fullname: req.Fullname, + Address: req.Address, + PhoneNumber: req.PhoneNumber, + WorkType: req.WorkType, + GenderType: req.GenderType, + IdentityType: req.IdentityType, + IdentityGroup: req.IdentityGroup, + IdentityGroupNumber: req.IdentityGroupNumber, + IdentityNumber: req.IdentityNumber, + DateOfBirth: req.DateOfBirth, + LastEducation: req.LastEducation, + Degree: req.Degree, + WhatsappNumber: req.WhatsappNumber, + LastJobTitle: req.LastJobTitle, + UserRoleId: req.UserRoleId, + UserLevelId: req.UserLevelId, + } +} + +type UsersUpdateRequest struct { + Username string `json:"username" validate:"required,lowercase"` + Email string `json:"email" validate:"required,email"` + Fullname string `json:"fullname" validate:"required"` + UserLevelId uint `json:"userLevelId" validate:"required"` + UserRoleId uint `json:"userRoleId" validate:"required"` + PhoneNumber *string `json:"phoneNumber"` + Address *string `json:"address"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + IdentityGroup *string `json:"identityGroup"` + IdentityGroupNumber *string `json:"identityGroupNumber"` + IdentityNumber *string `json:"identityNumber"` + DateOfBirth *string `json:"dateOfBirth"` + LastEducation *string `json:"lastEducation"` + Degree *string `json:"degree"` + WhatsappNumber *string `json:"whatsappNumber"` + LastJobTitle *string `json:"lastJobTitle"` + StatusId *int `json:"statusId"` +} + +func (req UsersUpdateRequest) ToEntity() *users.Users { + return &users.Users{ + Username: req.Username, + Email: req.Email, + Fullname: req.Fullname, + Address: req.Address, + PhoneNumber: req.PhoneNumber, + WorkType: req.WorkType, + GenderType: req.GenderType, + IdentityType: req.IdentityType, + IdentityGroup: req.IdentityGroup, + IdentityGroupNumber: req.IdentityGroupNumber, + IdentityNumber: req.IdentityNumber, + DateOfBirth: req.DateOfBirth, + LastEducation: req.LastEducation, + Degree: req.Degree, + WhatsappNumber: req.WhatsappNumber, + LastJobTitle: req.LastJobTitle, + UserRoleId: req.UserRoleId, + StatusId: req.StatusId, + UserLevelId: req.UserLevelId, + UpdatedAt: time.Now(), + } +} + +type UserLogin struct { + Username *string `json:"username"` + Password *string `json:"password"` + RefreshToken *string `json:"refreshToken"` +} + +type UserForgotPassword struct { + Username string `json:"username"` +} + +type UserSavePassword struct { + Password string `json:"password"` + ConfirmPassword string `json:"confirmPassword"` +} + +type UserResetPassword struct { + CodeRequest string `json:"codeRequest"` + UserId string `json:"userId"` + Password string `json:"password"` + ConfirmPassword string `json:"confirmPassword"` +} + +type UserEmailValidationRequest struct { + Username *string `json:"username"` + Password *string `json:"password"` + OldEmail *string `json:"oldEmail"` + NewEmail *string `json:"newEmail"` +} + +type UserOtpRequest struct { + Email string `json:"email" validate:"required,email"` + Name *string `json:"name"` +} + +type UserOtpValidation struct { + Email *string `json:"email"` + Username *string `json:"username"` + OtpCode string `json:"otpCode"` +} + +type UsersQueryRequestContext struct { + Username string `json:"username"` + Email string `json:"email"` + Fullname string `json:"fullname"` + PhoneNumber string `json:"phoneNumber"` + WorkType string `json:"workType"` + GenderType string `json:"genderType"` + IdentityType string `json:"identityType"` + IdentityGroup string `json:"identityGroup"` + IdentityGroupNumber string `json:"identityGroupNumber"` + IdentityNumber string `json:"identityNumber"` + Degree string `json:"degree"` + UserRoleId string `json:"userRoleId"` + StatusId string `json:"statusId"` +} + +func (req UsersQueryRequestContext) ToParamRequest() UsersQueryRequest { + var request UsersQueryRequest + + if username := req.Username; username != "" { + request.Username = &username + } + if email := req.Email; email != "" { + request.Email = &email + } + if fullname := req.Fullname; fullname != "" { + request.Fullname = &fullname + } + if phoneNumber := req.PhoneNumber; phoneNumber != "" { + request.PhoneNumber = &phoneNumber + } + if workType := req.WorkType; workType != "" { + request.WorkType = &workType + } + if genderType := req.GenderType; genderType != "" { + request.GenderType = &genderType + } + if identityType := req.IdentityType; identityType != "" { + request.IdentityType = &identityType + } + if identityGroup := req.IdentityGroup; identityGroup != "" { + request.IdentityGroup = &identityGroup + } + if identityGroupNumber := req.IdentityGroupNumber; identityGroupNumber != "" { + request.IdentityGroupNumber = &identityGroupNumber + } + if identityNumber := req.IdentityNumber; identityNumber != "" { + request.IdentityNumber = &identityNumber + } + if degree := req.Degree; degree != "" { + request.Degree = °ree + } + if userRoleIdStr := req.UserRoleId; userRoleIdStr != "" { + userRoleId, err := strconv.Atoi(userRoleIdStr) + if err == nil { + request.UserRoleId = &userRoleId + } + } + if statusIdStr := req.StatusId; statusIdStr != "" { + statusId, err := strconv.Atoi(statusIdStr) + if err == nil { + request.StatusId = &statusId + } + } + + return request +} diff --git a/app/module/users/response/users.response.go b/app/module/users/response/users.response.go new file mode 100644 index 0000000..12f39e3 --- /dev/null +++ b/app/module/users/response/users.response.go @@ -0,0 +1,38 @@ +package response + +import "time" + +type UsersResponse struct { + ID uint `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Fullname string `json:"fullname"` + Address *string `json:"address"` + PhoneNumber *string `json:"phoneNumber"` + WhatsappNumber *string `json:"whatsappNumber"` + WorkType *string `json:"workType"` + GenderType *string `json:"genderType"` + IdentityType *string `json:"identityType"` + Degree *string `json:"degree"` + IdentityNumber *string `json:"identityNumber"` + DateOfBirth *string `json:"dateOfBirth"` + LastEducation *string `json:"lastEducation"` + KeycloakId *string `json:"keycloakId"` + UserRoleId uint `json:"userRoleId"` + UserLevelId uint `json:"userLevelId"` + UserLevelGroup string `json:"userLevelGroup"` + StatusId *int `json:"statusId"` + CreatedById *uint `json:"createdById"` + ProfilePicturePath *string `json:"profilePicturePath"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type ParetoLoginResponse struct { + AccessToken string `json:"accessToken"` +} + +type VisitorStatistic struct { + TotalVisitor string `json:"accessToken"` +} diff --git a/app/module/users/service/users.service.go b/app/module/users/service/users.service.go new file mode 100644 index 0000000..292643e --- /dev/null +++ b/app/module/users/service/users.service.go @@ -0,0 +1,554 @@ +package service + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/database/entity/users" + userLevelsRepository "campaign-pool-be/app/module/user_levels/repository" + "campaign-pool-be/app/module/users/mapper" + "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/app/module/users/request" + "campaign-pool-be/app/module/users/response" + "campaign-pool-be/config/config" + "campaign-pool-be/utils/paginator" + utilSvc "campaign-pool-be/utils/service" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" + + paseto "aidanwoods.dev/go-paseto" + "github.com/Nerzal/gocloak/v13" + "github.com/rs/zerolog" +) + +// UsersService +type usersService struct { + Repo repository.UsersRepository + UserLevelsRepo userLevelsRepository.UserLevelsRepository + Log zerolog.Logger + Keycloak *config.KeycloakConfig + Smtp *config.SmtpConfig +} + +// UsersService define interface of IUsersService +type UsersService interface { + All(req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) + Show(id uint) (users *response.UsersResponse, err error) + ShowByUsername(username string) (users *response.UsersResponse, err error) + ShowUserInfo(authToken string) (users *response.UsersResponse, err error) + Save(req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) + Login(req request.UserLogin) (res *gocloak.JWT, err error) + ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) + Update(id uint, req request.UsersUpdateRequest) (err error) + Delete(id uint) error + SavePassword(req request.UserSavePassword, authToken string) (err error) + ResetPassword(req request.UserResetPassword) (err error) + ForgotPassword(req request.UserForgotPassword) (err error) + EmailValidationPreLogin(req request.UserEmailValidationRequest) (msgResponse *string, err error) + SetupEmail(req request.UserEmailValidationRequest) (msgResponse *string, err error) + OtpRequest(req request.UserOtpRequest) (err error) + OtpValidation(req request.UserOtpValidation) (err error) + SendLoginOtp(name string, email string, otp string) error + SendRegistrationOtp(name string, email string, otp string) error +} + +// NewUsersService init UsersService +func NewUsersService(repo repository.UsersRepository, userLevelsRepo userLevelsRepository.UserLevelsRepository, log zerolog.Logger, keycloak *config.KeycloakConfig, smtp *config.SmtpConfig) UsersService { + + return &usersService{ + Repo: repo, + UserLevelsRepo: userLevelsRepo, + Log: log, + Keycloak: keycloak, + Smtp: smtp, + } +} + +// All implement interface of UsersService +func (_i *usersService) All(req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo)) + } + + return +} + +func (_i *usersService) Show(id uint) (users *response.UsersResponse, err error) { + result, err := _i.Repo.FindOne(id) + if err != nil { + return nil, err + } + + return mapper.UsersResponseMapper(result, _i.UserLevelsRepo), nil +} + +func (_i *usersService) ShowByUsername(username string) (users *response.UsersResponse, err error) { + result, err := _i.Repo.FindByUsername(username) + if err != nil { + return nil, err + } + + return mapper.UsersResponseMapper(result, _i.UserLevelsRepo), nil +} + +func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersResponse, err error) { + userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + + return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo), nil +} + +func (_i *usersService) Save(req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + _i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("") + + if authToken != "" { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + newReq.CreatedById = &createdBy.ID + } + + keycloakId, err := _i.Keycloak.CreateUser(req.Fullname, req.Email, req.Username, req.Password) + if err != nil { + return nil, err + } + + newReq.KeycloakId = &keycloakId + newReq.TempPassword = &req.Password + + return _i.Repo.Create(newReq) +} + +func (_i *usersService) Login(req request.UserLogin) (res *gocloak.JWT, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + if req.RefreshToken == nil { + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + } else { + loginResponse, err = _i.Keycloak.RefreshToken(*req.RefreshToken) + } + if err != nil { + return nil, err + } + return loginResponse, nil +} + +func (_i *usersService) ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + token := paseto.NewToken() + secretKeyHex := "bdc42b1a0ba2bac3e27ba84241f9de06dee71b70f838af8d1beb0417f03d1d00" + secretKey, _ := paseto.V4SymmetricKeyFromHex(secretKeyHex) + // secretKey := paseto.NewV4SymmetricKey() // to change the secretKey periodically + + if req.RefreshToken == nil { + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + } else { + // Retrieve Refresh Token + parser := paseto.NewParser() + verifiedToken, err := parser.ParseV4Local(secretKey, *req.RefreshToken, nil) + if err != nil { + panic(err) + } + + refreshToken, _ := verifiedToken.GetString("refresh_token") + _i.Log.Info().Interface("Pareto parse refresh token", refreshToken).Msg("") + + loginResponse, err = _i.Keycloak.RefreshToken(refreshToken) + } + + _i.Log.Info().Interface("loginResponse", loginResponse).Msg("") + + if err != nil { + return nil, err + } + + parseToken, err := ParseJWTToken(loginResponse.AccessToken) + if err != nil { + return nil, err + } + + issuedAt := parseToken["iat"].(float64) + expirationTime := parseToken["exp"].(float64) + issuer := parseToken["iss"].(string) + jti := parseToken["jti"].(string) + subject := parseToken["sub"].(string) + + token.SetIssuedAt(time.Unix(int64(issuedAt), 0)) + token.SetNotBefore(time.Unix(int64(issuedAt), 0)) + token.SetExpiration(time.Unix(int64(expirationTime), 0)) + token.SetIssuer(issuer) + token.SetJti(jti) + token.SetSubject(subject) + token.SetString("access_token", loginResponse.AccessToken) + token.SetString("refresh_token", loginResponse.RefreshToken) + + _i.Log.Info().Interface("Pareto Generated Key", secretKey.ExportHex()).Msg("") + + tokenEncrypted := token.V4Encrypt(secretKey, nil) + + parser := paseto.NewParser() + verifiedToken, err := parser.ParseV4Local(secretKey, tokenEncrypted, nil) + if err != nil { + panic(err) + } + + tokenParsing, _ := verifiedToken.GetString("access_token") + _i.Log.Info().Interface("Pareto parse token", tokenParsing).Msg("") + + resLogin := &response.ParetoLoginResponse{ + AccessToken: tokenEncrypted, + } + + if err != nil { + return nil, err + } + + return resLogin, nil +} + +func (_i *usersService) Update(id uint, req request.UsersUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + newReq := req.ToEntity() + + findUser, err := _i.Repo.FindOne(id) + if err != nil { + return err + } + + err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email) + if err != nil { + return err + } + + return _i.Repo.Update(id, newReq) +} + +func (_i *usersService) 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 *usersService) SavePassword(req request.UserSavePassword, authToken string) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + _i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("") + + if authToken != "" { + createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken) + + tokenString := strings.TrimPrefix(authToken, "Bearer ") + + err := _i.Keycloak.SetPassword(tokenString, *createdBy.KeycloakId, req.Password) + if err != nil { + return err + } + + return nil + } else { + return fmt.Errorf("Invalid token") + } +} + +func (_i *usersService) ResetPassword(req request.UserResetPassword) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + if req.Password != req.ConfirmPassword { + return fmt.Errorf("Invalid Password") + } + + user, err := _i.Repo.FindByKeycloakId(req.UserId) + if err != nil { + return fmt.Errorf("User Id Not Found") + } + + forgotPassword, err := _i.Repo.FindForgotPassword(req.UserId, req.CodeRequest) + if err != nil { + return fmt.Errorf("Invalid Request") + } + + _i.Log.Info().Interface("data", forgotPassword).Msg("") + + _i.Log.Info().Interface("dataForgotPassword", forgotPassword).Msg("") + + if user != nil { + err := _i.Keycloak.SetPasswordWithoutToken(req.UserId, req.Password) + if err != nil { + return err + } + + forgotPassword.IsActive = false + forgotPassword.UpdatedAt = time.Now() + err = _i.Repo.UpdateForgotPassword(forgotPassword.ID, forgotPassword) + if err != nil { + return err + } + + return nil + } else { + return fmt.Errorf("User not found") + } +} + +func (_i *usersService) ForgotPassword(req request.UserForgotPassword) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + user, err := _i.Repo.FindByUsername(req.Username) + if err != nil { + return err + } + + if user != nil { + codeRequest, err := utilSvc.GenerateNumericCode(8) + if err != nil { + return nil + } + forgotPasswordReq := entity.ForgotPasswords{ + KeycloakID: *user.KeycloakId, + CodeRequest: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateForgotPassword(&forgotPasswordReq) + if err != nil { + return err + } + + // send email forgot password + url := fmt.Sprintf("https://kontenhumas.com/setup-password?userId=%s&code=%s", *user.KeycloakId, codeRequest) + + subject := "[HUMAS POLRI] Forgot Password" + htmlBody := "

Anda telah mengirimkan permintaan untuk melakukan reset password.

" + htmlBody += "

Silahkan buat password akun anda dengan menekan tombol di bawah ini, untuk membuat password baru

" + htmlBody += fmt.Sprintf("Reset Password", url) + htmlBody += "

Terimakasih.

" + err = _i.Smtp.SendEmail(subject, user.Email, user.Fullname, htmlBody) + + return nil + } else { + return fmt.Errorf("User not found") + } +} + +func (_i *usersService) OtpRequest(req request.UserOtpRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + codeRequest, err := utilSvc.GenerateNumericCode(6) + if req.Name == nil { + req.Name = &req.Email + } + if err != nil { + return err + } + otpReq := entity.OneTimePasswords{ + Email: req.Email, + Name: req.Name, + OtpCode: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateOtp(&otpReq) + if err != nil { + return err + } + + err = _i.SendRegistrationOtp(*req.Name, req.Email, codeRequest) + if err != nil { + return err + } + + return nil +} + +func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var otp *entity.OneTimePasswords + if req.Email == nil { + otp, err = _i.Repo.FindOtpByIdentity(*req.Username, req.OtpCode) + if err != nil { + return fmt.Errorf("OTP is not valid") + } + } else { + otp, err = _i.Repo.FindOtpByEmail(*req.Email, req.OtpCode) + if err != nil { + return fmt.Errorf("OTP is not valid") + } + } + + if otp != nil { + if otp.ValidUntil.Before(time.Now()) { + return fmt.Errorf("OTP has expired") + } + + return nil + } else { + return fmt.Errorf("OTP is not valid") + } +} + +func (_i *usersService) EmailValidationPreLogin(req request.UserEmailValidationRequest) (msgResponse *string, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + + if loginResponse == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + findUser, err := _i.Repo.FindByUsername(*req.Username) + if findUser == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + _i.Log.Info().Interface("data user", findUser).Msg("") + + if *findUser.IsEmailUpdated != true { + message := "Continue to setup email" + msgResponse = &message + } else { + codeRequest, err := utilSvc.GenerateNumericCode(6) + if err != nil { + return nil, err + } + otpReq := entity.OneTimePasswords{ + Email: findUser.Email, + Identity: &findUser.Username, + OtpCode: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateOtp(&otpReq) + if err != nil { + return nil, err + } + + err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest) + if err != nil { + return nil, err + } else { + msg := "Email is valid and OTP has been sent" + msgResponse = &msg + } + } + + return msgResponse, nil +} + +func (_i *usersService) SetupEmail(req request.UserEmailValidationRequest) (msgResponse *string, err error) { + _i.Log.Info().Interface("data", req).Msg("") + + var loginResponse *gocloak.JWT + loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password) + + if loginResponse == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + _i.Log.Info().Interface("findUser", "").Msg("") + findUser, err := _i.Repo.FindByUsername(*req.Username) + + _i.Log.Info().Interface("findUser", findUser).Msg("") + if findUser == nil || err != nil { + return nil, fmt.Errorf("username / password incorrect") + } + + isTrue := true + if findUser.Email == *req.OldEmail { + findUser.Email = *req.NewEmail + findUser.IsEmailUpdated = &isTrue + _i.Log.Info().Interface("Update", "").Msg("") + err = _i.Repo.Update(findUser.ID, findUser) + _i.Log.Info().Interface("Update", err).Msg("") + if err != nil { + return nil, err + } + + codeRequest, err := utilSvc.GenerateNumericCode(6) + if err != nil { + return nil, err + } + otpReq := entity.OneTimePasswords{ + Email: findUser.Email, + Identity: &findUser.Username, + OtpCode: codeRequest, + IsActive: true, + } + + err = _i.Repo.CreateOtp(&otpReq) + if err != nil { + return nil, err + } + + err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest) + if err != nil { + return nil, err + } else { + msg := "Email is valid and OTP has been sent" + msgResponse = &msg + } + } else { + return nil, fmt.Errorf("the old email is not same") + } + + return msgResponse, nil +} + +func ParseJWTToken(token string) (map[string]interface{}, error) { + // Pisahkan JWT menjadi 3 bagian: header, payload, dan signature + parts := strings.Split(token, ".") + if len(parts) != 3 { + return nil, fmt.Errorf("Invalid JWT token") + } + + // Decode bagian payload (index ke-1) + payloadData, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, fmt.Errorf("Failed to decode payload: %v", err) + } + + // Ubah payload menjadi map[string]interface{} + var payload map[string]interface{} + if err := json.Unmarshal(payloadData, &payload); err != nil { + return nil, fmt.Errorf("Failed to parse payload JSON: %v", err) + } + + return payload, nil +} + +func (_i *usersService) SendLoginOtp(name string, email string, otp string) error { + subject := "[HUMAS POLRI] Permintaan OTP" + htmlBody := fmt.Sprintf("

Hai %s !

Berikut kode OTP yang digunakan untuk Login.

", name) + htmlBody += fmt.Sprintf("

%s

", otp) + htmlBody += "

Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.

" + htmlBody += "

Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.

" + err := _i.Smtp.SendEmail(subject, email, name, htmlBody) + + return err +} + +func (_i *usersService) SendRegistrationOtp(name string, email string, otp string) error { + subject := "[HUMAS POLRI] Permintaan OTP" + htmlBody := fmt.Sprintf("

Hai %s !

Berikut kode OTP yang digunakan untuk Verifikasi Registrasi.

", name) + htmlBody += fmt.Sprintf("

%s

", otp) + htmlBody += "

Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.

" + htmlBody += "

Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.

" + err := _i.Smtp.SendEmail(subject, email, name, htmlBody) + + return err +} diff --git a/app/module/users/users.module.go b/app/module/users/users.module.go new file mode 100644 index 0000000..86f825b --- /dev/null +++ b/app/module/users/users.module.go @@ -0,0 +1,65 @@ +package users + +import ( + "campaign-pool-be/app/module/users/controller" + "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/app/module/users/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of UsersRouter +type UsersRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Users module +var NewUsersModule = fx.Options( + // register repository of Users module + fx.Provide(repository.NewUsersRepository), + + // register service of Users module + fx.Provide(service.NewUsersService), + + // register controller of Users module + fx.Provide(controller.NewController), + + // register router of Users module + fx.Provide(NewUsersRouter), +) + +// init UsersRouter +func NewUsersRouter(fiber *fiber.App, controller *controller.Controller) *UsersRouter { + return &UsersRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Users module +func (_i *UsersRouter) RegisterUsersRoutes() { + // define controllers + usersController := _i.Controller.Users + + // define routes + _i.App.Route("/users", func(router fiber.Router) { + router.Get("/", usersController.All) + router.Get("/detail/:id", usersController.Show) + router.Get("/username/:username", usersController.ShowByUsername) + router.Get("/info", usersController.ShowInfo) + router.Post("/", usersController.Save) + router.Put("/:id", usersController.Update) + router.Post("/login", usersController.Login) + router.Post("/pareto-login", usersController.ParetoLogin) + router.Delete("/:id", usersController.Delete) + router.Post("/save-password", usersController.SavePassword) + router.Post("/reset-password", usersController.ResetPassword) + router.Post("/forgot-password", usersController.ForgotPassword) + router.Post("/otp-request", usersController.OtpRequest) + router.Post("/otp-validation", usersController.OtpValidation) + router.Post("/email-validation", usersController.EmailValidation) + router.Post("/setup-email", usersController.SetupEmail) + }) +} diff --git a/app/module/work_history/controller/controller.go b/app/module/work_history/controller/controller.go new file mode 100644 index 0000000..f731a7c --- /dev/null +++ b/app/module/work_history/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "campaign-pool-be/app/module/work_history/service" + +type Controller struct { + WorkHistory WorkHistoryController +} + +func NewController(WorkHistoryService service.WorkHistoryService) *Controller { + return &Controller{ + WorkHistory: NewWorkHistoryController(WorkHistoryService), + } +} diff --git a/app/module/work_history/controller/work_history.controller.go b/app/module/work_history/controller/work_history.controller.go new file mode 100644 index 0000000..2b231f0 --- /dev/null +++ b/app/module/work_history/controller/work_history.controller.go @@ -0,0 +1,218 @@ +package controller + +import ( + "campaign-pool-be/app/module/work_history/request" + "campaign-pool-be/app/module/work_history/service" + "campaign-pool-be/utils/paginator" + utilRes "campaign-pool-be/utils/response" + utilVal "campaign-pool-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type workHistoryController struct { + workHistoryService service.WorkHistoryService +} + +type WorkHistoryController 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 NewWorkHistoryController(workHistoryService service.WorkHistoryService) WorkHistoryController { + return &workHistoryController{ + workHistoryService: workHistoryService, + } +} + +// All Work History +// @Summary Get all Work History +// @Description API for getting all Work History for specific user +// @Tags Work History +// @Param req query request.WorkHistoryQueryRequest false "query parameters" +// @Param req query paginator.Pagination false "pagination parameters" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /work-history [get] +func (_i *workHistoryController) All(c *fiber.Ctx) error { + paginate, err := paginator.Paginate(c) + if err != nil { + return err + } + reqContext := request.WorkHistoryQueryRequestContext{ + JobTitle: c.Query("jobTitle"), + CompanyName: c.Query("companyName"), + IsCurrent: c.Query("isCurrent"), + UserID: c.Query("userId"), + } + req := reqContext.ToParamRequest() + req.Pagination = paginate + + workData, paging, err := _i.workHistoryService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Work history list successfully retrieved"}, + Data: workData, + Meta: paging, + }) +} + +// Show Work History +// @Summary Get one Work History +// @Description API for getting one Work History +// @Tags Work History +// @Param id path int true "Work History ID" +// @Param userId query uint true "User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /work-history/{id} [get] +func (_i *workHistoryController) Show(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + workData, err := _i.workHistoryService.Show(uint(userId), uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Work history successfully retrieved"}, + Data: workData, + }) +} + +// Save Work History +// @Summary Create Work History +// @Description API for create Work History +// @Tags Work History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param userId query uint true "User ID" +// @Param payload body request.WorkHistoryCreateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /work-history [post] +func (_i *workHistoryController) Save(c *fiber.Ctx) error { + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + req := new(request.WorkHistoryCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + dataResult, err := _i.workHistoryService.Save(uint(userId), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Work history successfully created"}, + Data: dataResult, + }) +} + +// Update Work History +// @Summary Update Work History +// @Description API for update Work History +// @Tags Work History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Work History ID" +// @Param userId query uint true "User ID" +// @Param payload body request.WorkHistoryUpdateRequest true "Required payload" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /work-history/{id} [put] +func (_i *workHistoryController) Update(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + req := new(request.WorkHistoryUpdateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + err = _i.workHistoryService.Update(uint(userId), uint(id), *req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Work history successfully updated"}, + }) +} + +// Delete Work History +// @Summary Delete Work History +// @Description API for delete Work History +// @Tags Work History +// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token" +// @Param id path int true "Work History ID" +// @Param userId query uint true "User ID" +// @Success 200 {object} response.Response +// @Failure 400 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /work-history/{id} [delete] +func (_i *workHistoryController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + userId, err := strconv.ParseUint(c.Query("userId"), 10, 0) + if err != nil { + return utilRes.Resp(c, utilRes.Response{ + Success: false, + Messages: utilRes.Messages{"userId parameter is required and must be a valid number"}, + }) + } + + err = _i.workHistoryService.Delete(uint(userId), uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Messages: utilRes.Messages{"Work history successfully deleted"}, + }) +} diff --git a/app/module/work_history/mapper/work_history.mapper.go b/app/module/work_history/mapper/work_history.mapper.go new file mode 100644 index 0000000..7549c74 --- /dev/null +++ b/app/module/work_history/mapper/work_history.mapper.go @@ -0,0 +1,59 @@ +package mapper + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/work_history/response" + "fmt" + "time" +) + +func WorkHistoryResponseMapper(workHistory *entity.WorkHistory) *response.WorkHistoryResponse { + result := &response.WorkHistoryResponse{ + ID: workHistory.ID, + UserID: workHistory.UserID, + JobTitle: workHistory.JobTitle, + CompanyName: workHistory.CompanyName, + StartDate: workHistory.StartDate, + EndDate: workHistory.EndDate, + IsCurrent: workHistory.EndDate == nil, + Duration: calculateDuration(workHistory.StartDate, workHistory.EndDate), + CreatedAt: workHistory.CreatedAt, + UpdatedAt: workHistory.UpdatedAt, + } + + if workHistory.User != nil { + result.User = &response.UserBasicInfo{ + ID: workHistory.User.ID, + Username: workHistory.User.Username, + Fullname: workHistory.User.Fullname, + Email: workHistory.User.Email, + } + } + + return result +} + +func calculateDuration(startDate time.Time, endDate *time.Time) string { + var end time.Time + if endDate == nil { + end = time.Now() + } else { + end = *endDate + } + + duration := end.Sub(startDate) + days := int(duration.Hours() / 24) + years := days / 365 + months := (days % 365) / 30 + + if years > 0 { + if months > 0 { + return fmt.Sprintf("%d tahun %d bulan", years, months) + } + return fmt.Sprintf("%d tahun", years) + } else if months > 0 { + return fmt.Sprintf("%d bulan", months) + } else { + return fmt.Sprintf("%d hari", days) + } +} diff --git a/app/module/work_history/repository/work_history.repository.go b/app/module/work_history/repository/work_history.repository.go new file mode 100644 index 0000000..1c4c20c --- /dev/null +++ b/app/module/work_history/repository/work_history.repository.go @@ -0,0 +1,88 @@ +package repository + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/entity" + "campaign-pool-be/app/module/work_history/request" + "campaign-pool-be/utils/paginator" +) + +type workHistoryRepository struct { + DB *database.Database +} + +type WorkHistoryRepository interface { + GetAll(userId uint, req request.WorkHistoryQueryRequest) (workHistories []*entity.WorkHistory, paging paginator.Pagination, err error) + FindOneByUserAndId(userId uint, id uint) (workHistory *entity.WorkHistory, err error) + Create(workHistory *entity.WorkHistory) (result *entity.WorkHistory, err error) + Update(userId uint, id uint, workHistory *entity.WorkHistory) (err error) + Delete(userId uint, id uint) (err error) +} + +func NewWorkHistoryRepository(db *database.Database) WorkHistoryRepository { + return &workHistoryRepository{ + DB: db, + } +} + +func (_i *workHistoryRepository) GetAll(userId uint, req request.WorkHistoryQueryRequest) (workHistories []*entity.WorkHistory, paging paginator.Pagination, err error) { + query := _i.DB.DB.Where("user_id = ?", userId) + + // Apply filters + if req.JobTitle != nil { + query = query.Where("job_title ILIKE ?", "%"+*req.JobTitle+"%") + } + if req.CompanyName != nil { + query = query.Where("company_name ILIKE ?", "%"+*req.CompanyName+"%") + } + if req.IsCurrent != nil { + if *req.IsCurrent { + query = query.Where("end_date IS NULL") + } else { + query = query.Where("end_date IS NOT NULL") + } + } + + // Include user relationship + query = query.Preload("User") + + // Order by start date desc (most recent first) + query = query.Order("start_date 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(&workHistories).Error + paging = *req.Pagination + + return +} + +func (_i *workHistoryRepository) FindOneByUserAndId(userId uint, id uint) (workHistory *entity.WorkHistory, err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Preload("User").First(&workHistory).Error + return +} + +func (_i *workHistoryRepository) Create(workHistory *entity.WorkHistory) (result *entity.WorkHistory, err error) { + err = _i.DB.DB.Create(workHistory).Error + if err != nil { + return nil, err + } + + // Reload with relationships + err = _i.DB.DB.Preload("User").First(&result, workHistory.ID).Error + return +} + +func (_i *workHistoryRepository) Update(userId uint, id uint, workHistory *entity.WorkHistory) (err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Updates(workHistory).Error + return +} + +func (_i *workHistoryRepository) Delete(userId uint, id uint) (err error) { + err = _i.DB.DB.Where("user_id = ? AND id = ?", userId, id).Delete(&entity.WorkHistory{}).Error + return +} diff --git a/app/module/work_history/request/work_history.request.go b/app/module/work_history/request/work_history.request.go new file mode 100644 index 0000000..84e557e --- /dev/null +++ b/app/module/work_history/request/work_history.request.go @@ -0,0 +1,85 @@ +package request + +import ( + "campaign-pool-be/app/database/entity" + "campaign-pool-be/utils/paginator" + "strconv" + "time" +) + +type WorkHistoryQueryRequest struct { + UserID uint `json:"userId" validate:"required"` + JobTitle *string `json:"jobTitle"` + CompanyName *string `json:"companyName"` + IsCurrent *bool `json:"isCurrent"` // filter for current job (EndDate is null) + Pagination *paginator.Pagination `json:"pagination"` +} + +type WorkHistoryCreateRequest struct { + JobTitle string `json:"jobTitle" validate:"required,min=2,max=255"` + CompanyName string `json:"companyName" validate:"required,min=2,max=255"` + StartDate time.Time `json:"startDate" validate:"required"` + EndDate *time.Time `json:"endDate"` +} + +func (req WorkHistoryCreateRequest) ToEntity(userId uint) *entity.WorkHistory { + return &entity.WorkHistory{ + UserID: userId, + JobTitle: req.JobTitle, + CompanyName: req.CompanyName, + StartDate: req.StartDate, + EndDate: req.EndDate, + } +} + +type WorkHistoryUpdateRequest struct { + JobTitle string `json:"jobTitle" validate:"required,min=2,max=255"` + CompanyName string `json:"companyName" validate:"required,min=2,max=255"` + StartDate time.Time `json:"startDate" validate:"required"` + EndDate *time.Time `json:"endDate"` +} + +func (req WorkHistoryUpdateRequest) ToEntity(userId uint) *entity.WorkHistory { + return &entity.WorkHistory{ + UserID: userId, + JobTitle: req.JobTitle, + CompanyName: req.CompanyName, + StartDate: req.StartDate, + EndDate: req.EndDate, + } +} + +type WorkHistoryQueryRequestContext struct { + UserID string `json:"userId"` + JobTitle string `json:"jobTitle"` + CompanyName string `json:"companyName"` + IsCurrent string `json:"isCurrent"` +} + +func (req WorkHistoryQueryRequestContext) ToParamRequest() WorkHistoryQueryRequest { + var request WorkHistoryQueryRequest + + if userId := req.UserID; userId != "" { + userIdUint, err := strconv.ParseUint(userId, 10, 0) + if err == nil { + request.UserID = uint(userIdUint) + } + } + if jobTitle := req.JobTitle; jobTitle != "" { + request.JobTitle = &jobTitle + } + if companyName := req.CompanyName; companyName != "" { + request.CompanyName = &companyName + } + if isCurrent := req.IsCurrent; isCurrent != "" { + if isCurrent == "true" { + isCurrentBool := true + request.IsCurrent = &isCurrentBool + } else if isCurrent == "false" { + isCurrentBool := false + request.IsCurrent = &isCurrentBool + } + } + + return request +} diff --git a/app/module/work_history/response/work_history.response.go b/app/module/work_history/response/work_history.response.go new file mode 100644 index 0000000..e8be493 --- /dev/null +++ b/app/module/work_history/response/work_history.response.go @@ -0,0 +1,26 @@ +package response + +import ( + "time" +) + +type WorkHistoryResponse struct { + ID uint `json:"id"` + UserID uint `json:"userId"` + JobTitle string `json:"jobTitle"` + CompanyName string `json:"companyName"` + StartDate time.Time `json:"startDate"` + EndDate *time.Time `json:"endDate"` + IsCurrent bool `json:"isCurrent"` // true if EndDate is null + Duration string `json:"duration"` // calculated duration string + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + User *UserBasicInfo `json:"user,omitempty"` +} + +type UserBasicInfo struct { + ID uint `json:"id"` + Username string `json:"username"` + Fullname string `json:"fullname"` + Email string `json:"email"` +} diff --git a/app/module/work_history/service/work_history.service.go b/app/module/work_history/service/work_history.service.go new file mode 100644 index 0000000..b6342fa --- /dev/null +++ b/app/module/work_history/service/work_history.service.go @@ -0,0 +1,111 @@ +package service + +import ( + usersRepository "campaign-pool-be/app/module/users/repository" + "campaign-pool-be/app/module/work_history/mapper" + "campaign-pool-be/app/module/work_history/repository" + "campaign-pool-be/app/module/work_history/request" + "campaign-pool-be/app/module/work_history/response" + "campaign-pool-be/utils/paginator" + "errors" + + "github.com/rs/zerolog" +) + +type workHistoryService struct { + Repo repository.WorkHistoryRepository + UsersRepo usersRepository.UsersRepository + Log zerolog.Logger +} + +type WorkHistoryService interface { + All(req request.WorkHistoryQueryRequest) (workHistories []*response.WorkHistoryResponse, paging paginator.Pagination, err error) + Show(userId uint, id uint) (workHistory *response.WorkHistoryResponse, err error) + Save(userId uint, req request.WorkHistoryCreateRequest) (workHistory *response.WorkHistoryResponse, err error) + Update(userId uint, id uint, req request.WorkHistoryUpdateRequest) (err error) + Delete(userId uint, id uint) error +} + +func NewWorkHistoryService(repo repository.WorkHistoryRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) WorkHistoryService { + return &workHistoryService{ + Repo: repo, + UsersRepo: usersRepo, + Log: log, + } +} + +func (_i *workHistoryService) All(req request.WorkHistoryQueryRequest) (workHistories []*response.WorkHistoryResponse, paging paginator.Pagination, err error) { + results, paging, err := _i.Repo.GetAll(req.UserID, req) + if err != nil { + return + } + + for _, result := range results { + workHistories = append(workHistories, mapper.WorkHistoryResponseMapper(result)) + } + + return +} + +func (_i *workHistoryService) Show(userId uint, id uint) (workHistory *response.WorkHistoryResponse, err error) { + result, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return nil, err + } + + return mapper.WorkHistoryResponseMapper(result), nil +} + +func (_i *workHistoryService) Save(userId uint, req request.WorkHistoryCreateRequest) (workHistory *response.WorkHistoryResponse, err error) { + _i.Log.Info().Interface("data", req).Msg("Creating work history") + + // Validate business rules + if req.EndDate != nil && req.EndDate.Before(req.StartDate) { + return nil, errors.New("end date cannot be before start date") + } + + entity := req.ToEntity(userId) + + result, err := _i.Repo.Create(entity) + if err != nil { + return nil, err + } + + return mapper.WorkHistoryResponseMapper(result), nil +} + +func (_i *workHistoryService) Update(userId uint, id uint, req request.WorkHistoryUpdateRequest) (err error) { + _i.Log.Info().Interface("data", req).Msg("Updating work history") + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("work history not found") + } + + // Validate business rules + if req.EndDate != nil && req.EndDate.Before(req.StartDate) { + return errors.New("end date cannot be before start date") + } + + entity := req.ToEntity(userId) + return _i.Repo.Update(userId, id, entity) +} + +func (_i *workHistoryService) Delete(userId uint, id uint) error { + _i.Log.Info().Uint("userId", userId).Uint("id", id).Msg("Deleting work history") + + // Check if record exists and belongs to user + existing, err := _i.Repo.FindOneByUserAndId(userId, id) + if err != nil { + return err + } + if existing == nil { + return errors.New("work history not found") + } + + return _i.Repo.Delete(userId, id) +} diff --git a/app/module/work_history/work_history.module.go b/app/module/work_history/work_history.module.go new file mode 100644 index 0000000..8284cab --- /dev/null +++ b/app/module/work_history/work_history.module.go @@ -0,0 +1,54 @@ +package work_history + +import ( + "campaign-pool-be/app/module/work_history/controller" + "campaign-pool-be/app/module/work_history/repository" + "campaign-pool-be/app/module/work_history/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of WorkHistoryRouter +type WorkHistoryRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of WorkHistory module +var NewWorkHistoryModule = fx.Options( + // register repository of WorkHistory module + fx.Provide(repository.NewWorkHistoryRepository), + + // register service of WorkHistory module + fx.Provide(service.NewWorkHistoryService), + + // register controller of WorkHistory module + fx.Provide(controller.NewController), + + // register router of WorkHistory module + fx.Provide(NewWorkHistoryRouter), +) + +// init WorkHistoryRouter +func NewWorkHistoryRouter(fiber *fiber.App, controller *controller.Controller) *WorkHistoryRouter { + return &WorkHistoryRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of WorkHistory module +func (_i *WorkHistoryRouter) RegisterWorkHistoryRoutes() { + // define controllers + workHistoryController := _i.Controller.WorkHistory + + // define routes + _i.App.Route("/work-history", func(router fiber.Router) { + router.Get("/", workHistoryController.All) + router.Get("/:id", workHistoryController.Show) + router.Post("/", workHistoryController.Save) + router.Put("/:id", workHistoryController.Update) + router.Delete("/:id", workHistoryController.Delete) + }) +} diff --git a/app/router/api.go b/app/router/api.go new file mode 100644 index 0000000..72e7276 --- /dev/null +++ b/app/router/api.go @@ -0,0 +1,199 @@ +package router + +import ( + "campaign-pool-be/app/module/activity_logs" + "campaign-pool-be/app/module/advertisement" + "campaign-pool-be/app/module/ai_chat" + "campaign-pool-be/app/module/article_approvals" + "campaign-pool-be/app/module/article_categories" + "campaign-pool-be/app/module/article_category_details" + "campaign-pool-be/app/module/article_comments" + "campaign-pool-be/app/module/article_files" + "campaign-pool-be/app/module/articles" + "campaign-pool-be/app/module/campaign_destinations" + "campaign-pool-be/app/module/campaign_types" + "campaign-pool-be/app/module/campaigns" + "campaign-pool-be/app/module/campaign_files" + "campaign-pool-be/app/module/chat" + "campaign-pool-be/app/module/cities" + "campaign-pool-be/app/module/custom_static_pages" + "campaign-pool-be/app/module/districts" + "campaign-pool-be/app/module/ebooks" + "campaign-pool-be/app/module/education_history" + "campaign-pool-be/app/module/feedbacks" + "campaign-pool-be/app/module/magazine_files" + "campaign-pool-be/app/module/magazines" + "campaign-pool-be/app/module/master_menus" + "campaign-pool-be/app/module/master_modules" + "campaign-pool-be/app/module/provinces" + "campaign-pool-be/app/module/research_journals" + "campaign-pool-be/app/module/subscription" + "campaign-pool-be/app/module/user_levels" + "campaign-pool-be/app/module/user_role_accesses" + "campaign-pool-be/app/module/user_roles" + "campaign-pool-be/app/module/users" + "campaign-pool-be/app/module/work_history" + "campaign-pool-be/config/config" + _ "campaign-pool-be/docs/swagger" + + swagger "github.com/arsmn/fiber-swagger/v2" + "github.com/gofiber/fiber/v2" +) + +type Router struct { + App fiber.Router + Cfg *config.Config + + ActivityLogsRouter *activity_logs.ActivityLogsRouter + AdvertisementRouter *advertisement.AdvertisementRouter + AIChatRouter *ai_chat.AIChatRouter + ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter + ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter + ArticleFilesRouter *article_files.ArticleFilesRouter + ArticleCommentsRouter *article_comments.ArticleCommentsRouter + ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter + ArticlesRouter *articles.ArticlesRouter + ChatRouter *chat.ChatRouter + CitiesRouter *cities.CitiesRouter + CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter + DistrictsRouter *districts.DistrictsRouter + EbooksRouter *ebooks.EbooksRouter + FeedbacksRouter *feedbacks.FeedbacksRouter + MagazineFilesRouter *magazine_files.MagazineFilesRouter + MagazinesRouter *magazines.MagazinesRouter + MasterMenusRouter *master_menus.MasterMenusRouter + MasterModulesRouter *master_modules.MasterModulesRouter + ProvincesRouter *provinces.ProvincesRouter + SubscriptionRouter *subscription.SubscriptionRouter + UserLevelsRouter *user_levels.UserLevelsRouter + UserRoleAccessesRouter *user_role_accesses.UserRoleAccessesRouter + UserRolesRouter *user_roles.UserRolesRouter + UsersRouter *users.UsersRouter + EducationHistoryRouter *education_history.EducationHistoryRouter + WorkHistoryRouter *work_history.WorkHistoryRouter + ResearchJournalsRouter *research_journals.ResearchJournalsRouter + CampaignTypesRouter *campaign_types.CampaignTypesRouter + CampaignDestinationsRouter *campaign_destinations.CampaignDestinationsRouter + CampaignsRouter *campaigns.CampaignsRouter + CampaignFilesRouter *campaign_files.CampaignFilesRouter +} + +func NewRouter( + fiber *fiber.App, + cfg *config.Config, + + activityLogsRouter *activity_logs.ActivityLogsRouter, + advertisementRouter *advertisement.AdvertisementRouter, + aiChatRouter *ai_chat.AIChatRouter, + articleCategoriesRouter *article_categories.ArticleCategoriesRouter, + articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter, + articleFilesRouter *article_files.ArticleFilesRouter, + articleCommentsRouter *article_comments.ArticleCommentsRouter, + articleApprovalsRouter *article_approvals.ArticleApprovalsRouter, + articlesRouter *articles.ArticlesRouter, + chatRouter *chat.ChatRouter, + citiesRouter *cities.CitiesRouter, + customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter, + districtsRouter *districts.DistrictsRouter, + ebooksRouter *ebooks.EbooksRouter, + feedbacksRouter *feedbacks.FeedbacksRouter, + magazineFilesRouter *magazine_files.MagazineFilesRouter, + magazinesRouter *magazines.MagazinesRouter, + masterMenuRouter *master_menus.MasterMenusRouter, + masterModuleRouter *master_modules.MasterModulesRouter, + provincesRouter *provinces.ProvincesRouter, + subscriptionRouter *subscription.SubscriptionRouter, + userLevelsRouter *user_levels.UserLevelsRouter, + userRoleAccessesRouter *user_role_accesses.UserRoleAccessesRouter, + userRolesRouter *user_roles.UserRolesRouter, + usersRouter *users.UsersRouter, + educationHistoryRouter *education_history.EducationHistoryRouter, + workHistoryRouter *work_history.WorkHistoryRouter, + researchJournalsRouter *research_journals.ResearchJournalsRouter, + campaignTypesRouter *campaign_types.CampaignTypesRouter, + campaignDestinationsRouter *campaign_destinations.CampaignDestinationsRouter, + campaignsRouter *campaigns.CampaignsRouter, + campaignFilesRouter *campaign_files.CampaignFilesRouter, +) *Router { + return &Router{ + App: fiber, + Cfg: cfg, + ActivityLogsRouter: activityLogsRouter, + AdvertisementRouter: advertisementRouter, + AIChatRouter: aiChatRouter, + ArticleCategoriesRouter: articleCategoriesRouter, + ArticleCategoryDetailsRouter: articleCategoryDetailsRouter, + ArticleFilesRouter: articleFilesRouter, + ArticleCommentsRouter: articleCommentsRouter, + ArticleApprovalsRouter: articleApprovalsRouter, + ArticlesRouter: articlesRouter, + ChatRouter: chatRouter, + CitiesRouter: citiesRouter, + CustomStaticPagesRouter: customStaticPagesRouter, + DistrictsRouter: districtsRouter, + EbooksRouter: ebooksRouter, + FeedbacksRouter: feedbacksRouter, + MagazineFilesRouter: magazineFilesRouter, + MagazinesRouter: magazinesRouter, + MasterMenusRouter: masterMenuRouter, + MasterModulesRouter: masterModuleRouter, + ProvincesRouter: provincesRouter, + SubscriptionRouter: subscriptionRouter, + UserLevelsRouter: userLevelsRouter, + UserRoleAccessesRouter: userRoleAccessesRouter, + UserRolesRouter: userRolesRouter, + UsersRouter: usersRouter, + EducationHistoryRouter: educationHistoryRouter, + WorkHistoryRouter: workHistoryRouter, + ResearchJournalsRouter: researchJournalsRouter, + CampaignTypesRouter: campaignTypesRouter, + CampaignDestinationsRouter: campaignDestinationsRouter, + CampaignsRouter: campaignsRouter, + CampaignFilesRouter: campaignFilesRouter, + } +} + +// Register routes +func (r *Router) Register() { + // Test Routes + r.App.Get("/ping", func(c *fiber.Ctx) error { + return c.SendString("Pong! 👋") + }) + + //Swagger Documentation + r.App.Get("/swagger/*", swagger.HandlerDefault) + + // Register routes of modules + r.ActivityLogsRouter.RegisterActivityLogsRoutes() + r.AdvertisementRouter.RegisterAdvertisementRoutes() + r.AIChatRouter.RegisterAIChatRoutes() + r.ArticleCategoriesRouter.RegisterArticleCategoriesRoutes() + r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes() + r.ArticleFilesRouter.RegisterArticleFilesRoutes() + r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes() + r.ArticlesRouter.RegisterArticlesRoutes() + r.ArticleCommentsRouter.RegisterArticleCommentsRoutes() + r.ChatRouter.RegisterChatRoutes() + r.CitiesRouter.RegisterCitiesRoutes() + r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes() + r.DistrictsRouter.RegisterDistrictsRoutes() + r.EbooksRouter.RegisterEbooksRoutes() + r.FeedbacksRouter.RegisterFeedbacksRoutes() + r.MagazinesRouter.RegisterMagazinesRoutes() + r.MagazineFilesRouter.RegisterMagazineFilesRoutes() + r.MasterMenusRouter.RegisterMasterMenusRoutes() + r.MasterModulesRouter.RegisterMasterModulesRoutes() + r.ProvincesRouter.RegisterProvincesRoutes() + r.SubscriptionRouter.RegisterSubscriptionRoutes() + r.UserLevelsRouter.RegisterUserLevelsRoutes() + r.UserRoleAccessesRouter.RegisterUserRoleAccessesRoutes() + r.UsersRouter.RegisterUsersRoutes() + r.UserRolesRouter.RegisterUserRolesRoutes() + r.EducationHistoryRouter.RegisterEducationHistoryRoutes() + r.WorkHistoryRouter.RegisterWorkHistoryRoutes() + r.ResearchJournalsRouter.RegisterResearchJournalsRoutes() + r.CampaignTypesRouter.RegisterCampaignTypesRoutes() + r.CampaignDestinationsRouter.RegisterCampaignDestinationsRoutes() + r.CampaignsRouter.RegisterCampaignsRoutes() + r.CampaignFilesRouter.RegisterCampaignFilesRoutes() +} diff --git a/config/config/index.config.go b/config/config/index.config.go new file mode 100644 index 0000000..5f2d2e4 --- /dev/null +++ b/config/config/index.config.go @@ -0,0 +1,175 @@ +package config + +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/compress" + "github.com/pelletier/go-toml/v2" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +type app = struct { + Name string `toml:"name"` + Host string `toml:"host"` + Domain string `toml:"domain"` + Port string `toml:"port"` + ExternalPort string `toml:"external-port"` + PrintRoutes bool `toml:"print-routes"` + Prefork bool `toml:"prefork"` + Production bool `toml:"production"` + IdleTimeout time.Duration `toml:"idle-timeout"` + BodyLimit int `toml:"body-limit"` +} + +// db struct config +type db = struct { + Postgres struct { + DSN string `toml:"dsn"` + LogMode string `toml:"log-mode"` + Migrate bool `toml:"migrate"` + Seed bool `toml:"seed"` + } +} + +// log struct config +type logger = struct { + TimeFormat string `toml:"time-format"` + Level zerolog.Level `toml:"level"` + Prettier bool `toml:"prettier"` +} + +// middleware +type middleware = struct { + Compress struct { + Enable bool + Level compress.Level + } + + Recover struct { + Enable bool + } + + Monitor struct { + Enable bool + Path string + } + + Pprof struct { + Enable bool + } + + Cors struct { + Enable bool + } + + Limiter struct { + Enable bool + Max int + Expiration time.Duration `toml:"expiration_seconds"` + } + + Csrf struct { + Enable bool + CookieName string `toml:"cookie-name"` + CookieSameSite string `toml:"cookie-same-site"` + CookieSecure bool `toml:"cookie-secure"` + CookieSessionOnly bool `toml:"cookie-session-only"` + CookieHttpOnly bool `toml:"cookie-http-only"` + } + + AuditTrails struct { + Enable bool + Retention int + } +} + +// minio struct config +type objectStorage = struct { + MinioStorage struct { + Endpoint string `toml:"endpoint"` + AccessKeyID string `toml:"access-key-id"` + SecretAccessKey string `toml:"secret-access-key"` + UseSSL bool `toml:"use-ssl"` + BucketName string `toml:"bucket-name"` + Location string `toml:"location"` + } +} + +type keycloak = struct { + Endpoint string `toml:"endpoint"` + Realm string `toml:"realm"` + ClientId string `toml:"client-id"` + ClientSecret string `toml:"client-secret"` + AdminUsername string `toml:"admin-username"` + AdminPassword string `toml:"admin-password"` +} + +type smtp = struct { + Host string `toml:"host"` + Port int `toml:"port"` + Username string `toml:"username"` + Password string `toml:"password"` + FromAddress string `toml:"from-address"` + FromName string `toml:"from-name"` +} + +type Config struct { + App app + DB db + Logger logger + Middleware middleware + ObjectStorage objectStorage + Keycloak keycloak + Smtp smtp +} + +// NewConfig : initialize config +func NewConfig() *Config { + config, err := ParseConfig("config") + if err != nil && !fiber.IsChild() { + // panic if config is not found + log.Panic().Err(err).Msg("config not found") + } + + return config +} + +// ParseConfig : func to parse config +func ParseConfig(name string, debug ...bool) (*Config, error) { + var ( + contents *Config + file []byte + err error + ) + + if len(debug) > 0 { + file, err = os.ReadFile(name) + } else { + _, b, _, _ := runtime.Caller(0) + // get base path + path := filepath.Dir(filepath.Dir(filepath.Dir(b))) + file, err = os.ReadFile(filepath.Join(path, "./config/toml/", name+".toml")) + } + + if err != nil { + return &Config{}, err + } + + err = toml.Unmarshal(file, &contents) + + return contents, err +} + +// ParseAddress : func to parse address +func ParseAddress(raw string) (host, port string) { + if i := strings.LastIndex(raw, ":"); i > 0 { + return raw[:i], raw[i+1:] + } + + return raw, "" +} diff --git a/config/config/keycloak.config.go b/config/config/keycloak.config.go new file mode 100644 index 0000000..0ccdfc8 --- /dev/null +++ b/config/config/keycloak.config.go @@ -0,0 +1,188 @@ +package config + +import ( + "context" + "errors" + + "github.com/Nerzal/gocloak/v13" +) + +// MinioSetup struct +type KeycloakConfig struct { + Cfg *Config +} + +func NewKeycloakConfig(cfg *Config) *KeycloakConfig { + keycloakSetup := &KeycloakConfig{ + Cfg: cfg, + } + + return keycloakSetup +} + +func (_keycloak *KeycloakConfig) Login(username string, password string) (*gocloak.JWT, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + loginResponse, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + username, + password, + ) + if err != nil { + return nil, errors.New("Invalid User Credentials") + } + + return loginResponse, nil +} + +func (_keycloak *KeycloakConfig) RefreshToken(refreshToken string) (*gocloak.JWT, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + loginResponse, err := client.RefreshToken( + ctx, + refreshToken, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + ) + if err != nil { + return nil, errors.New("Invalid User Credentials") + } + + return loginResponse, nil +} + +func (_keycloak *KeycloakConfig) CreateUser(fullname string, email string, username string, password string) (string, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + var group []string + group = append(group, "narasi-ahli") + user := gocloak.User{ + FirstName: gocloak.StringP(fullname), + LastName: gocloak.StringP(fullname), + Email: gocloak.StringP(email), + Enabled: gocloak.BoolP(true), + Username: gocloak.StringP(username), + Groups: &group, + } + + keycloakId, err := client.CreateUser(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, user) + if err != nil { + panic("Oh no!, failed to create user :(") + } + + err = _keycloak.SetPassword(token.AccessToken, keycloakId, password) + if err != nil { + return "", err + } + + return keycloakId, nil +} + +func (_keycloak *KeycloakConfig) UpdateUser(keycloakId *string, fullname string, email string) error { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + var group []string + group = append(group, "narasi-ahli") + user := gocloak.User{ + ID: keycloakId, + FirstName: gocloak.StringP(fullname), + LastName: gocloak.StringP(fullname), + Email: gocloak.StringP(email), + Groups: &group, + } + + err = client.UpdateUser(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, user) + if err != nil { + panic(err) + } + + return err +} + +func (_keycloak *KeycloakConfig) SetPassword(token string, keycloakId string, password string) error { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + + err := client.SetPassword(ctx, token, keycloakId, _keycloak.Cfg.Keycloak.Realm, password, false) + if err != nil { + panic("Oh no!, failed to set password :(") + } + + return nil +} + +func (_keycloak *KeycloakConfig) SetPasswordWithoutToken(keycloakId string, password string) error { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + err = client.SetPassword(ctx, token.AccessToken, keycloakId, _keycloak.Cfg.Keycloak.Realm, password, false) + if err != nil { + panic("Oh no!, failed to set password :(") + } + + return nil +} + +func (_keycloak *KeycloakConfig) GetUserSessions() ([]*gocloak.UserSessionRepresentation, error) { + ctx := context.Background() + client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint) + + token, err := client.Login( + ctx, + _keycloak.Cfg.Keycloak.ClientId, + _keycloak.Cfg.Keycloak.ClientSecret, + _keycloak.Cfg.Keycloak.Realm, + _keycloak.Cfg.Keycloak.AdminUsername, + _keycloak.Cfg.Keycloak.AdminPassword, + ) + if err != nil { + panic("Something wrong with the credentials or url") + } + + sessionData, err := client.GetClientUserSessions(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, _keycloak.Cfg.Keycloak.ClientId) + if err != nil { + panic("Oh no!, failed to set password :(") + } + + return sessionData, nil +} diff --git a/config/config/minio.config.go b/config/config/minio.config.go new file mode 100644 index 0000000..883b257 --- /dev/null +++ b/config/config/minio.config.go @@ -0,0 +1,55 @@ +package config + +import ( + "context" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "log" +) + +// MinioSetup struct +type MinioStorage struct { + Cfg *Config +} + +func NewMinio(cfg *Config) *MinioStorage { + minioSetup := &MinioStorage{ + Cfg: cfg, + } + + return minioSetup +} + +func (_minio *MinioStorage) ConnectMinio() (*minio.Client, error) { + ctx := context.Background() + endpoint := _minio.Cfg.ObjectStorage.MinioStorage.Endpoint + accessKeyID := _minio.Cfg.ObjectStorage.MinioStorage.AccessKeyID + secretAccessKey := _minio.Cfg.ObjectStorage.MinioStorage.SecretAccessKey + useSSL := _minio.Cfg.ObjectStorage.MinioStorage.UseSSL + + bucketName := _minio.Cfg.ObjectStorage.MinioStorage.BucketName + location := _minio.Cfg.ObjectStorage.MinioStorage.Location + + // Initialize minio client object. + minioClient, errInit := minio.New(endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: useSSL, + }) + if errInit != nil { + log.Fatalln(errInit) + } + + err := minioClient.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: location}) + if err != nil { + // Check to see if we already own this bucket (which happens if you run this twice) + exists, errBucketExists := minioClient.BucketExists(ctx, bucketName) + if errBucketExists == nil && exists { + log.Printf("We already own %s\n", bucketName) + } else { + log.Fatalln(err) + } + } else { + log.Printf("Successfully created %s\n", bucketName) + } + return minioClient, errInit +} diff --git a/config/config/smtp.config.go b/config/config/smtp.config.go new file mode 100644 index 0000000..a376074 --- /dev/null +++ b/config/config/smtp.config.go @@ -0,0 +1,64 @@ +package config + +import ( + "fmt" + "github.com/wneessen/go-mail" + ht "html/template" +) + +// StmpConfig struct +type SmtpConfig struct { + Cfg *Config +} + +func NewSmtpConfig(cfg *Config) *SmtpConfig { + smtpSetup := &SmtpConfig{ + Cfg: cfg, + } + + return smtpSetup +} + +func (_smtp *SmtpConfig) SendEmail(subject string, toAddress string, toName string, htmlBody string) (err error) { + println(_smtp.Cfg.Smtp.Host) + println(_smtp.Cfg.Smtp.Port) + println(_smtp.Cfg.Smtp.Username) + println(_smtp.Cfg.Smtp.Password) + println(subject) + println(toAddress) + println(toName) + println(htmlBody) + + client, err := mail.NewClient(_smtp.Cfg.Smtp.Host, mail.WithPort(_smtp.Cfg.Smtp.Port), mail.WithSSL(), + mail.WithSMTPAuth(mail.SMTPAuthPlain), mail.WithUsername(_smtp.Cfg.Smtp.Username), mail.WithPassword(_smtp.Cfg.Smtp.Password)) + if err != nil { + return fmt.Errorf("failed to create mail client: %s\n", err) + } + + message := mail.NewMsg() + + if err := message.EnvelopeFrom(_smtp.Cfg.Smtp.FromAddress); err != nil { + return fmt.Errorf("failed to set ENVELOPE FROM address: %s", err) + } + if err := message.FromFormat(_smtp.Cfg.Smtp.FromName, _smtp.Cfg.Smtp.FromAddress); err != nil { + return fmt.Errorf("failed to set formatted FROM address: %s", err) + } + if err := message.AddToFormat(toName, toAddress); err != nil { + return fmt.Errorf("failed to set formatted TO address: %s", err) + } + + message.SetMessageID() + message.SetDate() + message.Subject(subject) + + htmlTpl, err := ht.New("htmltpl").Parse(htmlBody) + if err := message.SetBodyHTMLTemplate(htmlTpl, toAddress); err != nil { + return fmt.Errorf("failed to add HTML template to mail body: %s", err) + } + + if err = client.DialAndSend(message); err != nil { + return fmt.Errorf("failed to send mail: %s\n", err) + } + + return nil +} diff --git a/config/logger/index.logger.go b/config/logger/index.logger.go new file mode 100644 index 0000000..7c39105 --- /dev/null +++ b/config/logger/index.logger.go @@ -0,0 +1,46 @@ +package logger + +import ( + "campaign-pool-be/config/config" + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +// NewLogger : initialize logger +func NewLogger(cfg *config.Config) zerolog.Logger { + zerolog.TimeFieldFormat = cfg.Logger.TimeFormat + + if cfg.Logger.Prettier { + //log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) + + logFile, _ := os.OpenFile( + "debug.log", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, + 0664, + ) + + multi := zerolog.MultiLevelWriter(os.Stdout, logFile) + log.Logger = zerolog.New(multi).With().Timestamp().Logger() + } + + zerolog.SetGlobalLevel(cfg.Logger.Level) + + return log.Hook(PreforkHook{}) +} + +// PreforkHook : prefer hook for zerologger +type PreforkHook struct{} + +func (h PreforkHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + //if fiber.IsChild() { + // e.Discard() + //} +} + +type StringerFunc func() string + +func (f StringerFunc) String() string { + return f() +} diff --git a/config/toml/config.toml b/config/toml/config.toml new file mode 100644 index 0000000..a45da24 --- /dev/null +++ b/config/toml/config.toml @@ -0,0 +1,82 @@ +# Configuration vars for cmd/app +[app] +name = "Fiber starter" +host = "http://38.47.180.165" +port = ":8800" +domain = "https://campaignpool.id/api" +external-port = ":8807" +idle-timeout = 5 # As seconds +print-routes = false +prefork = true +production = false +body-limit = 1048576000 # "100 * 1024 * 1024" + +[db.postgres] +dsn = "postgresql://campaign_pool_user:CampaignPoolDB@2025@38.47.180.165:5432/campaign_pool_db" # ://:@:/ +log-mode = "NONE" +migrate = true +seed = true + +[logger] +log-dir = "debug.log" +time-format = "" # https://pkg.go.dev/time#pkg-constants, https://github.com/rs/zerolog/blob/master/api.go#L10 +level = 0 # panic -> 5, fatal -> 4, error -> 3, warn -> 2, info -> 1, debug -> 0, trace -> -1 +prettier = true + +[objectstorage.miniostorage] +endpoint = "38.47.180.165:9009" +access-key-id = "lBtjqWidHz1ktBbduwGy" +secret-access-key = "nsedJIa2FI7SqsEVcSFqJrlP4JuFRWGLauNpzD0i" +use-ssl = false +bucket-name = "campaign-pool" +location = "us-east-1" + +[middleware.compress] +enable = true +level = 1 + +[middleware.recover] +enable = true + +[middleware.monitor] +enable = true +path = "/monitor" + +[middleware.pprof] +enable = true + +[middleware.cors] +enable = true + +[middleware.limiter] +enable = true +max = 20 +expiration_seconds = 60 + +[middleware.csrf] +enable = false +cookie-name = "csrf_" +cookie-same-site = "Lax" +cookie-secure = false +cookie-session-only = true +cookie-http-only = true + +[middleware.audittrails] +enable = true +retention = 30 + +[keycloak] +endpoint = "http://38.47.180.165:8008" +realm = "campaign-pool" +client-id = "campaign-pool-app" +client-secret = "Im3rutPrquNULGmzPegmvxKyzeDYe6Vl" +admin-username = "developer-admin" +admin-password = "P@ssw0rd.1" + +[smtp] +host = "mail.polri.go.id" +port = 465 +username = "webhumas.divhumas@polri.go.id" +password = "8vm1nxxTsaB6" +from-address = "webhumas.divhumas@polri.go.id" +from-name = "APLIKASI WEB HUMAS DIVHUMAS POLRI" \ No newline at end of file diff --git a/config/webserver/webserver.config.go b/config/webserver/webserver.config.go new file mode 100644 index 0000000..4b41826 --- /dev/null +++ b/config/webserver/webserver.config.go @@ -0,0 +1,185 @@ +package webserver + +import ( + "context" + "flag" + "fmt" + "campaign-pool-be/app/database" + "campaign-pool-be/app/database/seeds" + md "campaign-pool-be/app/middleware" + articlesService "campaign-pool-be/app/module/articles/service" + "campaign-pool-be/app/router" + "campaign-pool-be/config/config" + "campaign-pool-be/utils/response" + "os" + "runtime" + "strings" + "time" + + "github.com/go-co-op/gocron" + "github.com/gofiber/fiber/v2" + futils "github.com/gofiber/fiber/v2/utils" + "github.com/rs/zerolog" + "go.uber.org/fx" +) + +// NewFiber : initialize the webserver +func NewFiber(cfg *config.Config) *fiber.App { + // setup + app := fiber.New(fiber.Config{ + ServerHeader: cfg.App.Name, + AppName: cfg.App.Name, + Prefork: cfg.App.Prefork, + ErrorHandler: response.ErrorHandler, + IdleTimeout: cfg.App.IdleTimeout * time.Second, + EnablePrintRoutes: cfg.App.PrintRoutes, + BodyLimit: cfg.App.BodyLimit, + DisableStartupMessage: true, + ReadBufferSize: 8192, + }) + + // pass production config to check it + response.IsProduction = cfg.App.Production + + return app +} + +// Start : function to start webserver +func Start(lifecycle fx.Lifecycle, cfg *config.Config, fiber *fiber.App, router *router.Router, middlewares *md.Middleware, db *database.Database, log zerolog.Logger) { + lifecycle.Append( + fx.Hook{ + OnStart: func(ctx context.Context) error { + + // Connect database + db.ConnectDatabase() + + // Register middlewares + middlewares.Register(db) + + // Register routes + router.Register() + + // Custom Startup Messages + host, port := config.ParseAddress(cfg.App.Port) + if host == "" { + if fiber.Config().Network == "tcp6" { + host = "[::1]" + } else { + host = "0.0.0.0" + } + } + + // ASCII Art + ascii, err := os.ReadFile("./storage/ascii_art.txt") + if err != nil { + log.Debug().Err(err).Msg("An unknown error occurred when to print ASCII art!") + } + + for _, line := range strings.Split(futils.UnsafeString(ascii), "\n") { + log.Info().Msg(line) + } + + // Information message + log.Info().Msg(fiber.Config().AppName + " is running at the moment!") + + // Debug informations + if !cfg.App.Production { + prefork := "Enabled" + procs := runtime.GOMAXPROCS(0) + if !cfg.App.Prefork { + procs = 1 + prefork = "Disabled" + } + + log.Debug().Msgf("Version: %s", "-") + log.Debug().Msgf("Host: %s", host) + log.Debug().Msgf("Port: %s", port) + log.Debug().Msgf("Prefork: %s", prefork) + log.Debug().Msgf("Handlers: %d", fiber.HandlersCount()) + log.Debug().Msgf("Processes: %d", procs) + log.Debug().Msgf("PID: %d", os.Getpid()) + } + + // Listen the app (with TLS Support) + //if cfg.App.TLS.Enable { + // log.Debug().Msg("TLS support was enabled.") + // + // if err := fiber.ListenTLS(cfg.App.Port, cfg.App.TLS.CertFile, cfg.App.TLS.KeyFile); err != nil { + // log.Error().Err(err).Msg("An unknown error occurred when to run server!") + // } + //} + + go func() { + if err := fiber.Listen(cfg.App.Port); err != nil { + log.Error().Err(err).Msg("An unknown error occurred when to run server!") + } + }() + + migrateFlag := flag.Bool("migrate", db.Cfg.DB.Postgres.Migrate, "migrate the database") + seedFlag := flag.Bool("seed", db.Cfg.DB.Postgres.Seed, "seed the database") + flag.Parse() + + // read flag -migrate to migrate the database + if *migrateFlag { + db.MigrateModels() + } + // read flag -seed to seed the database + if *seedFlag { + + // init seed models + masterStatusSeeder := seeds.MasterStatusesSeeder{} + masterApprovalStatusSeeder := seeds.MasterApprovalStatusesSeeder{} + activityLogsSeeder := seeds.ActivityLogsSeeder{} + allSeeders := []database.Seeder{masterStatusSeeder, masterApprovalStatusSeeder, activityLogsSeeder} + + db.SeedModels(allSeeders) + } + + return nil + }, + OnStop: func(ctx context.Context) error { + log.Info().Msg("Shutting down the app...") + if err := fiber.Shutdown(); err != nil { + log.Panic().Err(err).Msg("") + } + + log.Info().Msg("Running cleanup tasks...") + log.Info().Msg("1- Shutdown the database") + db.ShutdownDatabase() + log.Info().Msgf("%s was successful shutdown.", cfg.App.Name) + log.Info().Msg("\u001b[96msee you again👋\u001b[0m") + + return nil + }, + }, + ) +} + +func RunScheduling(lifecycle fx.Lifecycle, articleService articlesService.ArticlesService) *gocron.Scheduler { + scheduler := gocron.NewScheduler(time.UTC) + + // Tambahkan tugas yang berjalan setiap 10 detik + // scheduler.Every(1).Minutes().SingletonMode().Do(func() { + // fmt.Println("Run Scheduler", time.Now()) + // err := articleService.ExecuteScheduling() + // if err != nil { + // fmt.Println("Scheduler Got An Error", time.Now()) + // } + // }) + + // Menambahkan lifecycle hooks di Fx + lifecycle.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + fmt.Println("Memulai scheduler...") + scheduler.StartAsync() // Mulai scheduler saat aplikasi dimulai + return nil + }, + OnStop: func(ctx context.Context) error { + fmt.Println("Menghentikan scheduler...") + scheduler.Stop() // Hentikan scheduler saat aplikasi dihentikan + return nil + }, + }) + + return scheduler +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4c90011 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.8" + +services: + jaecoo-be: + image: registry.gitlab.com/hanifsalafi/jaecoo-be:dev + build: + context: . + dockerfile: Dockerfile + volumes: + - ./data/jaecoo-be/logs:/app + ports: + - "8800:8800" \ No newline at end of file diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go new file mode 100644 index 0000000..892b020 --- /dev/null +++ b/docs/swagger/docs.go @@ -0,0 +1,18104 @@ +// Package swagger Code generated by swaggo/swag. DO NOT EDIT +package swagger + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/activity-logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get all ActivityLogs", + "parameters": [ + { + "type": "integer", + "name": "activityTypeId", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "url", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Create ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/activity-logs/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get one ActivityLogs", + "parameters": [ + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/activity-logs/statistics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for get activity stats ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get activity stats ActivityLogs", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/activity-logs/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "update ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "delete ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Get all Advertisement", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "placement", + "in": "query" + }, + { + "type": "string", + "name": "redirectLink", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Create Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/publish/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Publish Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Update Publish Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Advertisement Publish Status", + "name": "isPublish", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/upload/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload File Advertisement", + "produces": [ + "application/json" + ], + "tags": [ + "Advertisement" + ], + "summary": "Upload Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Viewer Advertisement", + "parameters": [ + { + "type": "string", + "description": "Content File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Get one Advertisement", + "parameters": [ + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "update Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementUpdateRequest" + } + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "delete Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all AI chat logs for authenticated user", + "tags": [ + "AI Chat" + ], + "summary": "Get user AI chat logs", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "name": "logType", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/logs/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one AI chat log", + "tags": [ + "AI Chat" + ], + "summary": "Get one AI chat log", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Log ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all AI chat sessions for authenticated user", + "tags": [ + "AI Chat" + ], + "summary": "Get user AI chat sessions", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "boolean", + "name": "isActive", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Create AI chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatSessionsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/messages": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for sending a message to an AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Send message to AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatMessagesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/messages/{messageId}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update AI chat message", + "tags": [ + "AI Chat" + ], + "summary": "Update AI chat message", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Message ID", + "name": "messageId", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatMessagesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete AI chat message", + "tags": [ + "AI Chat" + ], + "summary": "Delete AI chat message", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Message ID", + "name": "messageId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Get one AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Update AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatSessionsUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Delete AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/{id}/messages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all messages in an AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Get AI chat session messages", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-approvals": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Get all ArticleApprovals", + "parameters": [ + { + "type": "integer", + "name": "approvalAtLevel", + "in": "query" + }, + { + "type": "integer", + "name": "approvalBy", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Create ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-approvals/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Get one ArticleApprovals", + "parameters": [ + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "update ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "delete ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get all ArticleCategories", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "UserLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "UserLevelNumber", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Create ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/old/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategories Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "ArticleCategories Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/thumbnail/viewer/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Viewer ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload ArticleCategories Thumbnail", + "produces": [ + "application/json" + ], + "tags": [ + "Article Categories" + ], + "summary": "Upload ArticleCategories Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "update ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "delete ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-category-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Get all ArticleCategoryDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Create ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-category-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Get one ArticleCategoryDetails", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "update ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "delete ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-comments": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get all ArticleComments", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "commentFrom", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublic", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Create ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-comments/approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Approval ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Approval ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsApprovalRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-comments/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get one ArticleComments", + "parameters": [ + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "update ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "delete ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Get all ArticleFiles", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "fileName", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/upload-status/{uploadId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for GetUploadStatus ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "GetUploadStatus ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Upload ID of ArticleFiles", + "name": "uploadId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Viewer ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Article File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleFiles", + "produces": [ + "application/json" + ], + "tags": [ + "Article Files" + ], + "summary": "Upload ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Get one ArticleFiles", + "parameters": [ + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Update ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleFilesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Delete ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Articles", + "tags": [ + "Articles" + ], + "summary": "Get all Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "name": "category", + "in": "query" + }, + { + "type": "integer", + "name": "categoryId", + "in": "query" + }, + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isBanner", + "in": "query" + }, + { + "type": "boolean", + "name": "isDraft", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "typeId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Articles", + "tags": [ + "Articles" + ], + "summary": "Create Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/banner/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Banner Articles", + "tags": [ + "Articles" + ], + "summary": "Update Banner Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Articles Banner Status", + "name": "isBanner", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/old-id/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": [ + "Articles" + ], + "summary": "Get one Articles", + "parameters": [ + { + "type": "integer", + "description": "Articles Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/publish-scheduling": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Publish Schedule of Article", + "tags": [ + "Articles" + ], + "summary": "PublishScheduling Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "query" + }, + { + "type": "string", + "description": "publish date", + "name": "date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticleMonthlyStats of Article", + "tags": [ + "Articles" + ], + "summary": "ArticleMonthlyStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Article", + "tags": [ + "Articles" + ], + "summary": "SummaryStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/statistic/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticlePerUserLevelStats of Article", + "tags": [ + "Articles" + ], + "summary": "ArticlePerUserLevelStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "start date", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "description": "start date", + "name": "endDate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Article", + "tags": [ + "Articles" + ], + "summary": "Viewer Articles Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Articles Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Articles", + "produces": [ + "application/json" + ], + "tags": [ + "Articles" + ], + "summary": "Save Thumbnail Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": [ + "Articles" + ], + "summary": "Get one Articles", + "parameters": [ + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Articles", + "tags": [ + "Articles" + ], + "summary": "Update Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesUpdateRequest" + } + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Articles", + "tags": [ + "Articles" + ], + "summary": "Delete Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-destinations": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Get all CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "campaignTypeId", + "in": "query" + }, + { + "type": "boolean", + "name": "isActive", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Create CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignDestinationsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-destinations/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Get one CampaignDestinations", + "parameters": [ + { + "type": "integer", + "description": "CampaignDestinations ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Update CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignDestinationsUpdateRequest" + } + }, + { + "type": "integer", + "description": "CampaignDestinations ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Delete CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "CampaignDestinations ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-types": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Get all CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Create CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignTypesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-types/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Get one CampaignTypes", + "parameters": [ + { + "type": "integer", + "description": "CampaignTypes ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Update CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignTypesUpdateRequest" + } + }, + { + "type": "integer", + "description": "CampaignTypes ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Delete CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "CampaignTypes ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaigns": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Get all Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "campaignTypeId", + "in": "query" + }, + { + "type": "integer", + "name": "creatorId", + "in": "query" + }, + { + "type": "string", + "name": "status", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Create Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaigns/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Get one Campaigns", + "parameters": [ + { + "type": "integer", + "description": "Campaigns ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Update Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignsUpdateRequest" + } + }, + { + "type": "integer", + "description": "Campaigns ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Delete Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Campaigns ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/messages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all messages in a specific chat session", + "tags": [ + "Chat" + ], + "summary": "Get all chat messages", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating a new chat message", + "tags": [ + "Chat" + ], + "summary": "Create chat message", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatMessageCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/messages/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat message", + "tags": [ + "Chat" + ], + "summary": "Get one chat message", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Message ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat message (only sender can update)", + "tags": [ + "Chat" + ], + "summary": "Update chat message", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Message ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatMessageUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat message (only sender can delete)", + "tags": [ + "Chat" + ], + "summary": "Delete chat message", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Message ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting files for a specific chat schedule", + "tags": [ + "Chat Schedule File" + ], + "summary": "Get chat schedule files", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "chatScheduleId", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "File type filter", + "name": "fileType", + "in": "query" + }, + { + "type": "boolean", + "description": "Required file filter", + "name": "isRequired", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for viewing chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "View chat schedule file", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Chat Schedule File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files/{chatScheduleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for uploading file for chat schedule", + "produces": [ + "application/json" + ], + "tags": [ + "Chat Schedule File" + ], + "summary": "Upload chat schedule file", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "chatScheduleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "Get one chat schedule file", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule File ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "Update chat schedule file", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule File ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatScheduleFileUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "Delete chat schedule file", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule File ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all chat schedules for authenticated user", + "tags": [ + "Chat Schedule" + ], + "summary": "Get all chat schedules", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "query" + }, + { + "type": "string", + "description": "Schedule status (scheduled, ongoing, completed, cancelled)", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Created by user ID", + "name": "createdBy", + "in": "query" + }, + { + "type": "string", + "description": "Date from (YYYY-MM-DD)", + "name": "dateFrom", + "in": "query" + }, + { + "type": "string", + "description": "Date to (YYYY-MM-DD)", + "name": "dateTo", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating a new chat schedule", + "tags": [ + "Chat Schedule" + ], + "summary": "Create chat schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatScheduleCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/status/{status}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting chat schedules by status", + "tags": [ + "Chat Schedule" + ], + "summary": "Get schedules by status", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Schedule status (scheduled, ongoing, completed, cancelled)", + "name": "status", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/upcoming": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting upcoming chat schedules for authenticated user", + "tags": [ + "Chat Schedule" + ], + "summary": "Get upcoming schedules", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "default": 10, + "description": "Limit number of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat schedule", + "tags": [ + "Chat Schedule" + ], + "summary": "Get one chat schedule", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat schedule (only creator can update)", + "tags": [ + "Chat Schedule" + ], + "summary": "Update chat schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatScheduleUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat schedule (only creator can delete)", + "tags": [ + "Chat Schedule" + ], + "summary": "Delete chat schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/{id}/reminder": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for sending reminder for a chat schedule (only creator can send)", + "tags": [ + "Chat Schedule" + ], + "summary": "Send schedule reminder", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/sessions": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all chat sessions for authenticated user", + "tags": [ + "Chat" + ], + "summary": "Get all chat sessions", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Chat type (personal or group)", + "name": "type", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating a new chat session (personal or group)", + "tags": [ + "Chat" + ], + "summary": "Create chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatSessionCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/sessions/{chatSessionId}/participants": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding a participant to a chat session (only creator can add)", + "tags": [ + "Chat" + ], + "summary": "Add participant to chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Participant User ID", + "name": "participantUserId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for removing a participant from a chat session (creator can remove anyone, user can remove themselves)", + "tags": [ + "Chat" + ], + "summary": "Remove participant from chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Participant User ID", + "name": "participantUserId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/sessions/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat session", + "tags": [ + "Chat" + ], + "summary": "Get one chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat session (only creator can update)", + "tags": [ + "Chat" + ], + "summary": "Update chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatSessionUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat session (only creator can delete)", + "tags": [ + "Chat" + ], + "summary": "Delete chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/cities": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Cities", + "tags": [ + "Untags" + ], + "summary": "Get all Cities", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Cities", + "tags": [ + "Untags" + ], + "summary": "Create Cities", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/cities/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Cities", + "tags": [ + "Untags" + ], + "summary": "Get one Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Cities", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Untags" + ], + "summary": "Update Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Cities", + "tags": [ + "Untags" + ], + "summary": "Delete Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/custom-static-pages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get all CustomStaticPages", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "htmlBody", + "in": "query" + }, + { + "type": "string", + "name": "slug", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Create CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/custom-static-pages/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "CustomStaticPages Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/custom-static-pages/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "update CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesUpdateRequest" + } + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "delete CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/districts": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Districts", + "tags": [ + "Untags" + ], + "summary": "Get all Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Districts", + "tags": [ + "Untags" + ], + "summary": "Create Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/districts/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Districts", + "tags": [ + "Untags" + ], + "summary": "Get one Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Districts", + "tags": [ + "Untags" + ], + "summary": "Update Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Districts", + "tags": [ + "Untags" + ], + "summary": "Delete Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/ebook-ratings": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebook Ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get all Ebook Ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "ebookId", + "in": "query" + }, + { + "type": "boolean", + "name": "isVerified", + "in": "query" + }, + { + "type": "integer", + "name": "rating", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Create ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/ebook/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ratings by ebook ID", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ratings by ebook ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/stats/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating statistics", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/summary/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating summary with stats and recent reviews", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating summary", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get user ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Update ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsUpdateRequest" + } + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Delete ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebooks", + "tags": [ + "Ebooks" + ], + "summary": "Get all Ebooks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "authorId", + "in": "query" + }, + { + "type": "string", + "name": "category", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublished", + "in": "query" + }, + { + "type": "number", + "name": "maxPrice", + "in": "query" + }, + { + "type": "number", + "name": "minPrice", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Create Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/download/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Download PDF of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Download PDF Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/pdf/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save PDF File of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save PDF File Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload PDF file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchase/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for purchasing ebook", + "tags": [ + "Ebook Purchase" + ], + "summary": "Purchase ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Payment Method", + "name": "paymentMethod", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user purchases", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get user purchases", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting purchase by ID", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get purchase by ID", + "parameters": [ + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/confirm": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for confirming payment", + "tags": [ + "Ebook Purchase" + ], + "summary": "Confirm payment", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/payment": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating payment status", + "tags": [ + "Ebook Purchase" + ], + "summary": "Update payment status", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.PaymentStatusUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook by slug", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook by slug", + "parameters": [ + { + "type": "string", + "description": "Ebook Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "SummaryStats Ebook", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save Thumbnail Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Get user wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/check/{ebookId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for checking if ebook is in wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Check if ebook is in wishlist", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding ebook to wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Add ebook to wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for removing ebook from wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Remove ebook from wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Update Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Delete Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/education-history": { + "get": { + "description": "API for getting all Education History for specific user", + "tags": [ + "Education History" + ], + "summary": "Get all Education History", + "parameters": [ + { + "type": "string", + "name": "educationLevel", + "in": "query" + }, + { + "type": "integer", + "name": "graduationYear", + "in": "query" + }, + { + "type": "string", + "name": "major", + "in": "query" + }, + { + "type": "string", + "name": "schoolName", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "description": "API for create Education History", + "tags": [ + "Education History" + ], + "summary": "Create Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EducationHistoryCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/education-history/{id}": { + "get": { + "description": "API for getting one Education History", + "tags": [ + "Education History" + ], + "summary": "Get one Education History", + "parameters": [ + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "description": "API for update Education History", + "tags": [ + "Education History" + ], + "summary": "Update Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EducationHistoryUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "description": "API for delete Education History", + "tags": [ + "Education History" + ], + "summary": "Delete Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/education-history/{id}/certificate": { + "post": { + "description": "API for upload certificate image for Education History", + "tags": [ + "Education History" + ], + "summary": "Upload Certificate for Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "type": "file", + "description": "Certificate image file", + "name": "certificate", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/feedbacks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Get all Feedbacks", + "parameters": [ + { + "type": "string", + "name": "commentFromEmail", + "in": "query" + }, + { + "type": "string", + "name": "commentFromName", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Create Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/feedbacks/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for FeedbackMonthlyStats of Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "FeedbackMonthlyStats Feedbacks", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/feedbacks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Get one Feedbacks", + "parameters": [ + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "update Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "delete Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Get all MagazineFiles", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Magazine File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Get one MagazineFiles", + "parameters": [ + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Update MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "delete MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files/{magazineId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file title", + "name": "title", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "magazineId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Magazines", + "tags": [ + "Magazines" + ], + "summary": "Get all Magazines", + "parameters": [ + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "pageUrl", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailPath", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailUrl", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Magazines", + "tags": [ + "Magazines" + ], + "summary": "Create Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Magazines", + "tags": [ + "Magazines" + ], + "summary": "Viewer Magazines Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Magazines Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Magazines", + "produces": [ + "application/json" + ], + "tags": [ + "Magazines" + ], + "summary": "Save Thumbnail Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Magazines", + "tags": [ + "Magazines" + ], + "summary": "Get one Magazines", + "parameters": [ + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Magazines", + "tags": [ + "Magazines" + ], + "summary": "Update Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Magazines", + "tags": [ + "Magazines" + ], + "summary": "Delete Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-menus": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Get all MasterMenus", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "integer", + "name": "moduleId", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentMenuId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Create MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterMenusCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-menus/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Get one MasterMenus", + "parameters": [ + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Update MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Delete MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-modules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Get all MasterModules", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Create MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-modules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Get one MasterModules", + "parameters": [ + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Update MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Delete MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-statuses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Get all MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Create MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/master-statuses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Get one MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Update MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Delete MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Provinces", + "tags": [ + "Untags" + ], + "summary": "Get all Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Provinces", + "tags": [ + "Untags" + ], + "summary": "Create Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Provinces", + "tags": [ + "Untags" + ], + "summary": "Get one Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Provinces", + "tags": [ + "Untags" + ], + "summary": "Update Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Provinces", + "tags": [ + "Untags" + ], + "summary": "Delete Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/research-journals": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Research Journals for authenticated user", + "tags": [ + "Research Journals" + ], + "summary": "Get all Research Journals", + "parameters": [ + { + "type": "string", + "name": "journalTitle", + "in": "query" + }, + { + "type": "integer", + "name": "publishedYear", + "in": "query" + }, + { + "type": "string", + "name": "publisher", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Create Research Journal", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ResearchJournalsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/research-journals/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Get one Research Journal", + "parameters": [ + { + "type": "integer", + "description": "Research Journal ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Update Research Journal", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Research Journal ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ResearchJournalsUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Delete Research Journal", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Research Journal ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/subscription": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Subscription", + "tags": [ + "Subscription" + ], + "summary": "Get all Subscription", + "parameters": [ + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Subscription", + "tags": [ + "Subscription" + ], + "summary": "Create Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/subscription/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Subscription", + "tags": [ + "Subscription" + ], + "summary": "Get one Subscription", + "parameters": [ + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Subscription", + "tags": [ + "Subscription" + ], + "summary": "update Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionUpdateRequest" + } + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Subscription", + "tags": [ + "Subscription" + ], + "summary": "delete Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get all UserLevels", + "parameters": [ + { + "type": "integer", + "name": "levelNumber", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "provinceId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Create UserLevels", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels/alias/{alias}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "string", + "description": "UserLevels Alias", + "name": "alias", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels/enable-approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Enable Approval of Article", + "tags": [ + "UserLevels" + ], + "summary": "EnableApproval Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsApprovalRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "update UserLevels", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "delete UserLevels", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-accesses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Get all UserRoleAccesses", + "parameters": [ + { + "type": "boolean", + "name": "isActive", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "menuId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Create UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-role-accesses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Get one UserRoleAccesses", + "parameters": [ + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "update UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "delete UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-role-level-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Get all UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Create UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-level-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Get one UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "update UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "delete UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-roles": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Get all UserRoles", + "parameters": [ + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Create UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-roles/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Get one UserRoles", + "parameters": [ + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "update UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "delete UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Users", + "tags": [ + "Users" + ], + "summary": "Get all Users", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "name": "degree", + "in": "query" + }, + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "string", + "name": "fullname", + "in": "query" + }, + { + "type": "string", + "name": "genderType", + "in": "query" + }, + { + "type": "string", + "name": "identityGroup", + "in": "query" + }, + { + "type": "string", + "name": "identityGroupNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityType", + "in": "query" + }, + { + "type": "string", + "name": "phoneNumber", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" + }, + { + "type": "string", + "name": "workType", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Users", + "tags": [ + "Users" + ], + "summary": "Create Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": [ + "Users" + ], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/email-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Email Validation Users", + "tags": [ + "Users" + ], + "summary": "EmailValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/forgot-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ForgotPassword Users", + "tags": [ + "Users" + ], + "summary": "ForgotPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserForgotPassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/info": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ShowUserInfo", + "tags": [ + "Users" + ], + "summary": "ShowInfo Users", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Login Users", + "tags": [ + "Users" + ], + "summary": "Login Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/otp-request": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpRequest Users", + "tags": [ + "Users" + ], + "summary": "OtpRequest Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/otp-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpValidation Users", + "tags": [ + "Users" + ], + "summary": "OtpValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpValidation" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/pareto-login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ParetoLogin Users", + "tags": [ + "Users" + ], + "summary": "ParetoLogin Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/reset-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ResetPassword Users", + "tags": [ + "Users" + ], + "summary": "ResetPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserResetPassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/save-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for SavePassword Users", + "tags": [ + "Users" + ], + "summary": "SavePassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserSavePassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/setup-email": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Setup Email Users", + "tags": [ + "Users" + ], + "summary": "SetupEmail Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/username/{username}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": [ + "Users" + ], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "description": "Username", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Users", + "tags": [ + "Users" + ], + "summary": "update Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Users", + "tags": [ + "Users" + ], + "summary": "delete Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/work-history": { + "get": { + "description": "API for getting all Work History for specific user", + "tags": [ + "Work History" + ], + "summary": "Get all Work History", + "parameters": [ + { + "type": "string", + "name": "companyName", + "in": "query" + }, + { + "type": "boolean", + "description": "filter for current job (EndDate is null)", + "name": "isCurrent", + "in": "query" + }, + { + "type": "string", + "name": "jobTitle", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "description": "API for create Work History", + "tags": [ + "Work History" + ], + "summary": "Create Work History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WorkHistoryCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/work-history/{id}": { + "get": { + "description": "API for getting one Work History", + "tags": [ + "Work History" + ], + "summary": "Get one Work History", + "parameters": [ + { + "type": "integer", + "description": "Work History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "description": "API for update Work History", + "tags": [ + "Work History" + ], + "summary": "Update Work History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Work History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WorkHistoryUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "description": "API for delete Work History", + "tags": [ + "Work History" + ], + "summary": "Delete Work History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Work History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + } + }, + "definitions": { + "controller.PaymentStatusUpdateRequest": { + "type": "object", + "required": [ + "paymentStatus" + ], + "properties": { + "paymentProof": { + "type": "string" + }, + "paymentStatus": { + "type": "string" + }, + "transactionId": { + "type": "string" + } + } + }, + "paginator.Pagination": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "nextPage": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "previousPage": { + "type": "integer" + }, + "sort": { + "type": "string" + }, + "sortBy": { + "type": "string" + }, + "totalPage": { + "type": "integer" + } + } + }, + "request.AIChatMessagesCreateRequest": { + "type": "object", + "required": [ + "content", + "messageType", + "sessionId" + ], + "properties": { + "content": { + "type": "string", + "minLength": 1 + }, + "messageType": { + "type": "string", + "enum": [ + "user", + "assistant" + ] + }, + "sessionId": { + "type": "string" + } + } + }, + "request.AIChatMessagesUpdateRequest": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "minLength": 1 + } + } + }, + "request.AIChatSessionsCreateRequest": { + "type": "object", + "required": [ + "agentId", + "sessionId", + "title" + ], + "properties": { + "agentId": { + "type": "string" + }, + "sessionId": { + "type": "string" + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.AIChatSessionsUpdateRequest": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "isActive": { + "type": "boolean" + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.ActivityLogsCreateRequest": { + "type": "object", + "required": [ + "activityTypeId", + "url" + ], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + }, + "visitorIp": { + "type": "string" + } + } + }, + "request.ActivityLogsUpdateRequest": { + "type": "object", + "required": [ + "activityTypeId", + "id", + "url" + ], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + } + } + }, + "request.AdvertisementCreateRequest": { + "type": "object", + "required": [ + "description", + "placement", + "redirectLink", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.AdvertisementUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "placement", + "redirectLink", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleApprovalsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleApprovalsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCategoriesCreateRequest": { + "type": "object", + "required": [ + "description", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "oldCategoryId": { + "type": "integer" + }, + "parentId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCategoriesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "parentId": { + "type": "integer" + }, + "publishedAt": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCommentsApprovalRequest": { + "type": "object", + "required": [ + "id", + "statusId" + ], + "properties": { + "id": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleFilesUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "isPublish", + "publishedAt", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "fileAlt": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "fileThumbnail": { + "type": "string" + }, + "fileUrl": { + "type": "string" + }, + "heightPixel": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "publishedAt": { + "type": "string" + }, + "size": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "widthPixel": { + "type": "string" + } + } + }, + "request.ArticlesCreateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "oldId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.ArticlesUpdateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.CampaignDestinationsCreateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "name" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "subType": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "request.CampaignDestinationsUpdateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "name" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "subType": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "request.CampaignTypesCreateRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.CampaignTypesUpdateRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.CampaignsCreateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "title" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "creatorId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "mediaItemSelected": { + "type": "string" + }, + "mediaPromote": { + "type": "boolean" + }, + "mediaTypeSelected": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.CampaignsUpdateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "title" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "mediaItemSelected": { + "type": "string" + }, + "mediaPromote": { + "type": "boolean" + }, + "mediaTypeSelected": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ChatMessageCreateRequest": { + "type": "object", + "required": [ + "chatSessionId", + "message" + ], + "properties": { + "chatSessionId": { + "type": "integer" + }, + "message": { + "type": "string", + "maxLength": 1000, + "minLength": 1 + }, + "messageType": { + "type": "string", + "enum": [ + "text", + "image", + "file", + "user", + "assistant" + ] + } + } + }, + "request.ChatMessageUpdateRequest": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "maxLength": 1000, + "minLength": 1 + } + } + }, + "request.ChatScheduleCreateRequest": { + "type": "object", + "required": [ + "scheduled_at", + "title" + ], + "properties": { + "chat_session_id": { + "description": "Optional - if empty, will create new chat session", + "type": "integer" + }, + "description": { + "type": "string", + "maxLength": 1000 + }, + "duration": { + "description": "15 minutes to 8 hours", + "type": "integer", + "maximum": 480, + "minimum": 15 + }, + "file_ids": { + "description": "Array of file IDs to attach to schedule", + "type": "array", + "items": { + "type": "integer" + } + }, + "scheduled_at": { + "type": "string" + }, + "summary": { + "type": "string", + "maxLength": 2000 + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 3 + } + } + }, + "request.ChatScheduleFileUpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "maxLength": 500 + }, + "file_name": { + "type": "string", + "maxLength": 255 + }, + "file_path": { + "type": "string", + "maxLength": 500 + }, + "file_size": { + "type": "integer", + "minimum": 0 + }, + "file_type": { + "type": "string", + "enum": [ + "article", + "journal", + "video", + "audio", + "document", + "other" + ] + }, + "is_required": { + "type": "boolean" + }, + "mime_type": { + "type": "string", + "maxLength": 100 + }, + "original_name": { + "type": "string", + "maxLength": 255 + } + } + }, + "request.ChatScheduleUpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "maxLength": 1000 + }, + "duration": { + "type": "integer", + "maximum": 480, + "minimum": 15 + }, + "file_ids": { + "description": "Array of file IDs to attach to schedule", + "type": "array", + "items": { + "type": "integer" + } + }, + "scheduled_at": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "scheduled", + "ongoing", + "completed", + "cancelled" + ] + }, + "summary": { + "type": "string", + "maxLength": 2000 + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 3 + } + } + }, + "request.ChatSessionCreateRequest": { + "type": "object", + "required": [ + "type", + "userIds" + ], + "properties": { + "name": { + "description": "null for personal chat", + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "type": { + "type": "string", + "enum": [ + "personal", + "group" + ] + }, + "userIds": { + "description": "participants (excluding creator)", + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + } + } + }, + "request.ChatSessionUpdateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.CitiesCreateRequest": { + "type": "object", + "required": [ + "cityName", + "provId" + ], + "properties": { + "cityName": { + "type": "string" + }, + "provId": { + "type": "integer" + } + } + }, + "request.CitiesUpdateRequest": { + "type": "object", + "required": [ + "cityName", + "id", + "provId" + ], + "properties": { + "cityName": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "provId": { + "type": "integer" + } + } + }, + "request.CustomStaticPagesCreateRequest": { + "type": "object", + "required": [ + "htmlBody", + "slug", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.CustomStaticPagesUpdateRequest": { + "type": "object", + "required": [ + "htmlBody", + "id", + "slug", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "request.EbookRatingsCreateRequest": { + "type": "object", + "required": [ + "ebookId", + "purchaseId", + "rating" + ], + "properties": { + "ebookId": { + "type": "integer" + }, + "isAnonymous": { + "type": "boolean" + }, + "purchaseId": { + "type": "integer" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbookRatingsUpdateRequest": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "isAnonymous": { + "type": "boolean" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbooksCreateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.EbooksUpdateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.EducationHistoryCreateRequest": { + "type": "object", + "required": [ + "educationLevel", + "graduationYear", + "major", + "schoolName" + ], + "properties": { + "certificateImage": { + "type": "string" + }, + "educationLevel": { + "type": "string", + "maxLength": 100, + "minLength": 2 + }, + "graduationYear": { + "type": "integer", + "maximum": 2030, + "minimum": 1950 + }, + "major": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "schoolName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.EducationHistoryUpdateRequest": { + "type": "object", + "required": [ + "educationLevel", + "graduationYear", + "major", + "schoolName" + ], + "properties": { + "certificateImage": { + "type": "string" + }, + "educationLevel": { + "type": "string", + "maxLength": 100, + "minLength": 2 + }, + "graduationYear": { + "type": "integer", + "maximum": 2030, + "minimum": 1950 + }, + "major": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "schoolName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.FeedbacksCreateRequest": { + "type": "object", + "required": [ + "commentFromEmail", + "commentFromName", + "message" + ], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "request.FeedbacksUpdateRequest": { + "type": "object", + "required": [ + "commentFromEmail", + "commentFromName", + "id", + "message" + ], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + } + } + }, + "request.MagazinesCreateRequest": { + "type": "object", + "required": [ + "description", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MagazinesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MasterMenusCreateRequest": { + "type": "object", + "required": [ + "description", + "group", + "moduleId", + "name", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "group": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "moduleId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentMenuId": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesCreateRequest": { + "type": "object", + "required": [ + "description", + "name", + "pathUrl", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "name", + "pathUrl", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ResearchJournalsCreateRequest": { + "type": "object", + "required": [ + "journalTitle", + "journalUrl", + "publisher" + ], + "properties": { + "journalTitle": { + "type": "string", + "maxLength": 500, + "minLength": 2 + }, + "journalUrl": { + "type": "string" + }, + "publishedDate": { + "type": "string" + }, + "publisher": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.ResearchJournalsUpdateRequest": { + "type": "object", + "required": [ + "journalTitle", + "journalUrl", + "publisher" + ], + "properties": { + "journalTitle": { + "type": "string", + "maxLength": 500, + "minLength": 2 + }, + "journalUrl": { + "type": "string" + }, + "publishedDate": { + "type": "string" + }, + "publisher": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.SubscriptionCreateRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + } + } + }, + "request.SubscriptionUpdateRequest": { + "type": "object", + "required": [ + "email", + "id" + ], + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "request.UserEmailValidationRequest": { + "type": "object", + "properties": { + "newEmail": { + "type": "string" + }, + "oldEmail": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserForgotPassword": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + }, + "request.UserLevelsApprovalRequest": { + "type": "object", + "required": [ + "ids", + "isApprovalActive" + ], + "properties": { + "ids": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + } + } + }, + "request.UserLevelsCreateRequest": { + "type": "object", + "required": [ + "aliasName", + "levelNumber", + "name" + ], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLevelsUpdateRequest": { + "type": "object", + "required": [ + "aliasName", + "levelNumber", + "name" + ], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLogin": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserOtpRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.UserOtpValidation": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "otpCode": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserResetPassword": { + "type": "object", + "properties": { + "codeRequest": { + "type": "string" + }, + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "request.UserRoleAccessesCreateRequest": { + "type": "object", + "required": [ + "isAdminEnabled", + "isApprovalEnabled", + "isDeleteEnabled", + "isInsertEnabled", + "isUpdateEnabled", + "isViewEnabled", + "menuId" + ], + "properties": { + "isAdminEnabled": { + "type": "boolean" + }, + "isApprovalEnabled": { + "type": "boolean" + }, + "isDeleteEnabled": { + "type": "boolean" + }, + "isInsertEnabled": { + "type": "boolean" + }, + "isUpdateEnabled": { + "type": "boolean" + }, + "isViewEnabled": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + } + } + }, + "request.UserRoleAccessesUpdateRequest": { + "type": "object", + "required": [ + "id", + "isAdminEnabled", + "isApprovalEnabled", + "isDeleteEnabled", + "isInsertEnabled", + "isUpdateEnabled", + "isViewEnabled", + "menuId", + "userRoleId" + ], + "properties": { + "id": { + "type": "integer" + }, + "isAdminEnabled": { + "type": "boolean" + }, + "isApprovalEnabled": { + "type": "boolean" + }, + "isDeleteEnabled": { + "type": "boolean" + }, + "isInsertEnabled": { + "type": "boolean" + }, + "isUpdateEnabled": { + "type": "boolean" + }, + "isViewEnabled": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + } + } + }, + "request.UserRolesCreateRequest": { + "type": "object", + "required": [ + "code", + "description", + "name", + "statusId", + "userLevelIds", + "userRoleAccess" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "userRoleAccess": { + "type": "array", + "items": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + } + }, + "request.UserRolesUpdateRequest": { + "type": "object", + "required": [ + "code", + "description", + "levelNumber", + "name", + "statusId", + "userLevelIds" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.UserSavePassword": { + "type": "object", + "properties": { + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "request.UsersCreateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "password", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "degree": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "lastJobTitle": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "whatsappNumber": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "request.UsersUpdateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "degree": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "lastJobTitle": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "whatsappNumber": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "request.WorkHistoryCreateRequest": { + "type": "object", + "required": [ + "companyName", + "jobTitle", + "startDate" + ], + "properties": { + "companyName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "endDate": { + "type": "string" + }, + "jobTitle": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "startDate": { + "type": "string" + } + } + }, + "request.WorkHistoryUpdateRequest": { + "type": "object", + "required": [ + "companyName", + "jobTitle", + "startDate" + ], + "properties": { + "companyName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "endDate": { + "type": "string" + }, + "jobTitle": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "startDate": { + "type": "string" + } + } + }, + "response.BadRequestError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 400 + }, + "message": { + "type": "string", + "example": "bad request" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.InternalServerError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 500 + }, + "message": { + "type": "string", + "example": "internal server error" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 200 + }, + "data": {}, + "messages": { + "type": "array", + "items": {} + }, + "meta": {}, + "success": { + "type": "boolean", + "example": true + } + } + }, + "response.UnauthorizedError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 401 + }, + "message": { + "type": "string", + "example": "unauthorized access" + }, + "success": { + "type": "boolean", + "example": false + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json new file mode 100644 index 0000000..c551734 --- /dev/null +++ b/docs/swagger/swagger.json @@ -0,0 +1,18075 @@ +{ + "swagger": "2.0", + "info": { + "contact": {} + }, + "paths": { + "/activity-logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get all ActivityLogs", + "parameters": [ + { + "type": "integer", + "name": "activityTypeId", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "url", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Create ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/activity-logs/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get one ActivityLogs", + "parameters": [ + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/activity-logs/statistics": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for get activity stats ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "Get activity stats ActivityLogs", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/activity-logs/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "update ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ActivityLogsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ActivityLogs", + "tags": [ + "ActivityLogs" + ], + "summary": "delete ActivityLogs", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ActivityLogs ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Get all Advertisement", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "placement", + "in": "query" + }, + { + "type": "string", + "name": "redirectLink", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Create Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/publish/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Publish Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Update Publish Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Advertisement Publish Status", + "name": "isPublish", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/upload/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload File Advertisement", + "produces": [ + "application/json" + ], + "tags": [ + "Advertisement" + ], + "summary": "Upload Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Viewer Advertisement", + "parameters": [ + { + "type": "string", + "description": "Content File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/advertisement/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "Get one Advertisement", + "parameters": [ + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "update Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AdvertisementUpdateRequest" + } + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Advertisement", + "tags": [ + "Advertisement" + ], + "summary": "delete Advertisement", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Advertisement ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/logs": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all AI chat logs for authenticated user", + "tags": [ + "AI Chat" + ], + "summary": "Get user AI chat logs", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "name": "logType", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/logs/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one AI chat log", + "tags": [ + "AI Chat" + ], + "summary": "Get one AI chat log", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Log ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all AI chat sessions for authenticated user", + "tags": [ + "AI Chat" + ], + "summary": "Get user AI chat sessions", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "boolean", + "name": "isActive", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Create AI chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatSessionsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/messages": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for sending a message to an AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Send message to AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatMessagesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/messages/{messageId}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update AI chat message", + "tags": [ + "AI Chat" + ], + "summary": "Update AI chat message", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Message ID", + "name": "messageId", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatMessagesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete AI chat message", + "tags": [ + "AI Chat" + ], + "summary": "Delete AI chat message", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Message ID", + "name": "messageId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Get one AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Update AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AIChatSessionsUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Delete AI chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ai-chat/sessions/{id}/messages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all messages in an AI chat session", + "tags": [ + "AI Chat" + ], + "summary": "Get AI chat session messages", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Session ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-approvals": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Get all ArticleApprovals", + "parameters": [ + { + "type": "integer", + "name": "approvalAtLevel", + "in": "query" + }, + { + "type": "integer", + "name": "approvalBy", + "in": "query" + }, + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Create ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-approvals/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "Get one ArticleApprovals", + "parameters": [ + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "update ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleApprovalsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleApprovals", + "tags": [ + "ArticleApprovals" + ], + "summary": "delete ArticleApprovals", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleApprovals ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get all ArticleCategories", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "UserLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "UserLevelNumber", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Create ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/old/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategories Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "ArticleCategories Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/thumbnail/viewer/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Viewer ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Upload ArticleCategories Thumbnail", + "produces": [ + "application/json" + ], + "tags": [ + "Article Categories" + ], + "summary": "Upload ArticleCategories Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-categories/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "Get one ArticleCategories", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "update ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCategoriesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategories", + "tags": [ + "Article Categories" + ], + "summary": "delete ArticleCategories", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategories ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-category-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Get all ArticleCategoryDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Create ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-category-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "Get one ArticleCategoryDetails", + "parameters": [ + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "update ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleCategoryDetails", + "tags": [ + "Untags" + ], + "summary": "delete ArticleCategoryDetails", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleCategoryDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/article-comments": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get all ArticleComments", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "integer", + "name": "commentFrom", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublic", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "integer", + "name": "parentId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Create ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-comments/approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Approval ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Approval ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsApprovalRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-comments/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "Get one ArticleComments", + "parameters": [ + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "update ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleCommentsUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleComments", + "tags": [ + "ArticleComments" + ], + "summary": "delete ArticleComments", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleComments ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Get all ArticleFiles", + "parameters": [ + { + "type": "integer", + "name": "articleId", + "in": "query" + }, + { + "type": "string", + "name": "fileName", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/upload-status/{uploadId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for GetUploadStatus ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "GetUploadStatus ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Upload ID of ArticleFiles", + "name": "uploadId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Viewer ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Viewer ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Article File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/{articleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create ArticleFiles", + "produces": [ + "application/json" + ], + "tags": [ + "Article Files" + ], + "summary": "Upload ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Article ID", + "name": "articleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/article-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Get one ArticleFiles", + "parameters": [ + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Update ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticleFilesUpdateRequest" + } + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete ArticleFiles", + "tags": [ + "Article Files" + ], + "summary": "Delete ArticleFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "ArticleFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Articles", + "tags": [ + "Articles" + ], + "summary": "Get all Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "name": "category", + "in": "query" + }, + { + "type": "integer", + "name": "categoryId", + "in": "query" + }, + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isBanner", + "in": "query" + }, + { + "type": "boolean", + "name": "isDraft", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "typeId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Articles", + "tags": [ + "Articles" + ], + "summary": "Create Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/banner/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Update Banner Articles", + "tags": [ + "Articles" + ], + "summary": "Update Banner Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "boolean", + "description": "Articles Banner Status", + "name": "isBanner", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/old-id/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": [ + "Articles" + ], + "summary": "Get one Articles", + "parameters": [ + { + "type": "integer", + "description": "Articles Old ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/publish-scheduling": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Publish Schedule of Article", + "tags": [ + "Articles" + ], + "summary": "PublishScheduling Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "article id", + "name": "id", + "in": "query" + }, + { + "type": "string", + "description": "publish date", + "name": "date", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticleMonthlyStats of Article", + "tags": [ + "Articles" + ], + "summary": "ArticleMonthlyStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Article", + "tags": [ + "Articles" + ], + "summary": "SummaryStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/statistic/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ArticlePerUserLevelStats of Article", + "tags": [ + "Articles" + ], + "summary": "ArticlePerUserLevelStats Articles", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "start date", + "name": "startDate", + "in": "query" + }, + { + "type": "string", + "description": "start date", + "name": "endDate", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Article", + "tags": [ + "Articles" + ], + "summary": "Viewer Articles Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Articles Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Articles", + "produces": [ + "application/json" + ], + "tags": [ + "Articles" + ], + "summary": "Save Thumbnail Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/articles/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Articles", + "tags": [ + "Articles" + ], + "summary": "Get one Articles", + "parameters": [ + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Articles", + "tags": [ + "Articles" + ], + "summary": "Update Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ArticlesUpdateRequest" + } + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Articles", + "tags": [ + "Articles" + ], + "summary": "Delete Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Articles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-destinations": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Get all CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "campaignTypeId", + "in": "query" + }, + { + "type": "boolean", + "name": "isActive", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Create CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignDestinationsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-destinations/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Get one CampaignDestinations", + "parameters": [ + { + "type": "integer", + "description": "CampaignDestinations ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Update CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignDestinationsUpdateRequest" + } + }, + { + "type": "integer", + "description": "CampaignDestinations ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CampaignDestinations", + "tags": [ + "CampaignDestinations" + ], + "summary": "Delete CampaignDestinations", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "CampaignDestinations ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-types": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Get all CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Create CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignTypesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaign-types/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Get one CampaignTypes", + "parameters": [ + { + "type": "integer", + "description": "CampaignTypes ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Update CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignTypesUpdateRequest" + } + }, + { + "type": "integer", + "description": "CampaignTypes ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CampaignTypes", + "tags": [ + "CampaignTypes" + ], + "summary": "Delete CampaignTypes", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "CampaignTypes ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaigns": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Get all Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "campaignTypeId", + "in": "query" + }, + { + "type": "integer", + "name": "creatorId", + "in": "query" + }, + { + "type": "string", + "name": "status", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Create Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/campaigns/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Get one Campaigns", + "parameters": [ + { + "type": "integer", + "description": "Campaigns ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Update Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CampaignsUpdateRequest" + } + }, + { + "type": "integer", + "description": "Campaigns ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Campaigns", + "tags": [ + "Campaigns" + ], + "summary": "Delete Campaigns", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Campaigns ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/messages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all messages in a specific chat session", + "tags": [ + "Chat" + ], + "summary": "Get all chat messages", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating a new chat message", + "tags": [ + "Chat" + ], + "summary": "Create chat message", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatMessageCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/messages/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat message", + "tags": [ + "Chat" + ], + "summary": "Get one chat message", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Message ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat message (only sender can update)", + "tags": [ + "Chat" + ], + "summary": "Update chat message", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Message ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatMessageUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat message (only sender can delete)", + "tags": [ + "Chat" + ], + "summary": "Delete chat message", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Message ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting files for a specific chat schedule", + "tags": [ + "Chat Schedule File" + ], + "summary": "Get chat schedule files", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "chatScheduleId", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "File type filter", + "name": "fileType", + "in": "query" + }, + { + "type": "boolean", + "description": "Required file filter", + "name": "isRequired", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for viewing chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "View chat schedule file", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Chat Schedule File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files/{chatScheduleId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for uploading file for chat schedule", + "produces": [ + "application/json" + ], + "tags": [ + "Chat Schedule File" + ], + "summary": "Upload chat schedule file", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "chatScheduleId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedule-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "Get one chat schedule file", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule File ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "Update chat schedule file", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule File ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatScheduleFileUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat schedule file", + "tags": [ + "Chat Schedule File" + ], + "summary": "Delete chat schedule file", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule File ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all chat schedules for authenticated user", + "tags": [ + "Chat Schedule" + ], + "summary": "Get all chat schedules", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "query" + }, + { + "type": "string", + "description": "Schedule status (scheduled, ongoing, completed, cancelled)", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Created by user ID", + "name": "createdBy", + "in": "query" + }, + { + "type": "string", + "description": "Date from (YYYY-MM-DD)", + "name": "dateFrom", + "in": "query" + }, + { + "type": "string", + "description": "Date to (YYYY-MM-DD)", + "name": "dateTo", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating a new chat schedule", + "tags": [ + "Chat Schedule" + ], + "summary": "Create chat schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatScheduleCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/status/{status}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting chat schedules by status", + "tags": [ + "Chat Schedule" + ], + "summary": "Get schedules by status", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Schedule status (scheduled, ongoing, completed, cancelled)", + "name": "status", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/upcoming": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting upcoming chat schedules for authenticated user", + "tags": [ + "Chat Schedule" + ], + "summary": "Get upcoming schedules", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "default": 10, + "description": "Limit number of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat schedule", + "tags": [ + "Chat Schedule" + ], + "summary": "Get one chat schedule", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat schedule (only creator can update)", + "tags": [ + "Chat Schedule" + ], + "summary": "Update chat schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatScheduleUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat schedule (only creator can delete)", + "tags": [ + "Chat Schedule" + ], + "summary": "Delete chat schedule", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/schedules/{id}/reminder": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for sending reminder for a chat schedule (only creator can send)", + "tags": [ + "Chat Schedule" + ], + "summary": "Send schedule reminder", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Schedule ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/sessions": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all chat sessions for authenticated user", + "tags": [ + "Chat" + ], + "summary": "Get all chat sessions", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "description": "Chat type (personal or group)", + "name": "type", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating a new chat session (personal or group)", + "tags": [ + "Chat" + ], + "summary": "Create chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatSessionCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/sessions/{chatSessionId}/participants": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding a participant to a chat session (only creator can add)", + "tags": [ + "Chat" + ], + "summary": "Add participant to chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Participant User ID", + "name": "participantUserId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for removing a participant from a chat session (creator can remove anyone, user can remove themselves)", + "tags": [ + "Chat" + ], + "summary": "Remove participant from chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "chatSessionId", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Participant User ID", + "name": "participantUserId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/chat/sessions/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one chat session", + "tags": [ + "Chat" + ], + "summary": "Get one chat session", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating chat session (only creator can update)", + "tags": [ + "Chat" + ], + "summary": "Update chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChatSessionUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting chat session (only creator can delete)", + "tags": [ + "Chat" + ], + "summary": "Delete chat session", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Chat Session ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/cities": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Cities", + "tags": [ + "Untags" + ], + "summary": "Get all Cities", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Cities", + "tags": [ + "Untags" + ], + "summary": "Create Cities", + "parameters": [ + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/cities/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Cities", + "tags": [ + "Untags" + ], + "summary": "Get one Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Cities", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Untags" + ], + "summary": "Update Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CitiesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Cities", + "tags": [ + "Untags" + ], + "summary": "Delete Cities", + "parameters": [ + { + "type": "integer", + "description": "Cities ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/custom-static-pages": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get all CustomStaticPages", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "htmlBody", + "in": "query" + }, + { + "type": "string", + "name": "slug", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Create CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/custom-static-pages/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "CustomStaticPages Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/custom-static-pages/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "Get one CustomStaticPages", + "parameters": [ + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "update CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomStaticPagesUpdateRequest" + } + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete CustomStaticPages", + "tags": [ + "CustomStaticPages" + ], + "summary": "delete CustomStaticPages", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "CustomStaticPages ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/districts": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Districts", + "tags": [ + "Untags" + ], + "summary": "Get all Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Districts", + "tags": [ + "Untags" + ], + "summary": "Create Districts", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/districts/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Districts", + "tags": [ + "Untags" + ], + "summary": "Get one Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Districts", + "tags": [ + "Untags" + ], + "summary": "Update Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Districts", + "tags": [ + "Untags" + ], + "summary": "Delete Districts", + "parameters": [ + { + "type": "integer", + "description": "Districts ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/ebook-ratings": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebook Ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get all Ebook Ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "ebookId", + "in": "query" + }, + { + "type": "boolean", + "name": "isVerified", + "in": "query" + }, + { + "type": "integer", + "name": "rating", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for creating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Create ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/ebook/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ratings by ebook ID", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ratings by ebook ID", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/stats/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating statistics", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating statistics", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/summary/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting ebook rating summary with stats and recent reviews", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get ebook rating summary", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/user": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user ratings", + "tags": [ + "Ebook Ratings" + ], + "summary": "Get user ratings", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebook-ratings/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Update ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbookRatingsUpdateRequest" + } + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for deleting ebook rating", + "tags": [ + "Ebook Ratings" + ], + "summary": "Delete ebook rating", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Rating ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Ebooks", + "tags": [ + "Ebooks" + ], + "summary": "Get all Ebooks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "authorId", + "in": "query" + }, + { + "type": "string", + "name": "category", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublished", + "in": "query" + }, + { + "type": "number", + "name": "maxPrice", + "in": "query" + }, + { + "type": "number", + "name": "minPrice", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "tags", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Create Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header" + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/download/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Download PDF of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Download PDF Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/pdf/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save PDF File of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save PDF File Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload PDF file", + "name": "file", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchase/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for purchasing ebook", + "tags": [ + "Ebook Purchase" + ], + "summary": "Purchase ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Payment Method", + "name": "paymentMethod", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user purchases", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get user purchases", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting purchase by ID", + "tags": [ + "Ebook Purchase" + ], + "summary": "Get purchase by ID", + "parameters": [ + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/confirm": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for confirming payment", + "tags": [ + "Ebook Purchase" + ], + "summary": "Confirm payment", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/purchases/{id}/payment": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for updating payment status", + "tags": [ + "Ebook Purchase" + ], + "summary": "Update payment status", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Purchase ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.PaymentStatusUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/slug/{slug}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook by slug", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook by slug", + "parameters": [ + { + "type": "string", + "description": "Ebook Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/statistic/summary": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Summary Stats of Ebook", + "tags": [ + "Ebooks" + ], + "summary": "SummaryStats Ebook", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Ebook", + "produces": [ + "application/json" + ], + "tags": [ + "Ebooks" + ], + "summary": "Save Thumbnail Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting user wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Get user wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/check/{ebookId}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for checking if ebook is in wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Check if ebook is in wishlist", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/wishlist/{ebookId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for adding ebook to wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Add ebook to wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for removing ebook from wishlist", + "tags": [ + "Ebook Wishlist" + ], + "summary": "Remove ebook from wishlist", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "ebookId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/ebooks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Get one Ebook", + "parameters": [ + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Update Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EbooksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Ebook", + "tags": [ + "Ebooks" + ], + "summary": "Delete Ebook", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Ebook ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/education-history": { + "get": { + "description": "API for getting all Education History for specific user", + "tags": [ + "Education History" + ], + "summary": "Get all Education History", + "parameters": [ + { + "type": "string", + "name": "educationLevel", + "in": "query" + }, + { + "type": "integer", + "name": "graduationYear", + "in": "query" + }, + { + "type": "string", + "name": "major", + "in": "query" + }, + { + "type": "string", + "name": "schoolName", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "description": "API for create Education History", + "tags": [ + "Education History" + ], + "summary": "Create Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EducationHistoryCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/education-history/{id}": { + "get": { + "description": "API for getting one Education History", + "tags": [ + "Education History" + ], + "summary": "Get one Education History", + "parameters": [ + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "description": "API for update Education History", + "tags": [ + "Education History" + ], + "summary": "Update Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.EducationHistoryUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "description": "API for delete Education History", + "tags": [ + "Education History" + ], + "summary": "Delete Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/education-history/{id}/certificate": { + "post": { + "description": "API for upload certificate image for Education History", + "tags": [ + "Education History" + ], + "summary": "Upload Certificate for Education History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Education History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "type": "file", + "description": "Certificate image file", + "name": "certificate", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/feedbacks": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Get all Feedbacks", + "parameters": [ + { + "type": "string", + "name": "commentFromEmail", + "in": "query" + }, + { + "type": "string", + "name": "commentFromName", + "in": "query" + }, + { + "type": "string", + "name": "endDate", + "in": "query" + }, + { + "type": "string", + "name": "message", + "in": "query" + }, + { + "type": "string", + "name": "startDate", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Create Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/feedbacks/statistic/monthly": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for FeedbackMonthlyStats of Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "FeedbackMonthlyStats Feedbacks", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "year", + "name": "year", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/feedbacks/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "Get one Feedbacks", + "parameters": [ + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "update Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.FeedbacksUpdateRequest" + } + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Feedbacks", + "tags": [ + "Feedbacks" + ], + "summary": "delete Feedbacks", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Feedbacks ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Get all MagazineFiles", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Magazine File Name", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Get one MagazineFiles", + "parameters": [ + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Update MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "delete MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MagazineFiles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazine-files/{magazineId}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MagazineFiles", + "tags": [ + "Magazine Files" + ], + "summary": "Create MagazineFiles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "file", + "description": "Upload file", + "name": "files", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file title", + "name": "title", + "in": "formData", + "required": true + }, + { + "type": "string", + "description": "Magazine file description", + "name": "description", + "in": "formData", + "required": true + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "magazineId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Magazines", + "tags": [ + "Magazines" + ], + "summary": "Get all Magazines", + "parameters": [ + { + "type": "integer", + "name": "createdById", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "boolean", + "name": "isPublish", + "in": "query" + }, + { + "type": "string", + "name": "pageUrl", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailPath", + "in": "query" + }, + { + "type": "string", + "name": "thumbnailUrl", + "in": "query" + }, + { + "type": "string", + "name": "title", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Magazines", + "tags": [ + "Magazines" + ], + "summary": "Create Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines/thumbnail/viewer/{thumbnailName}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for View Thumbnail of Magazines", + "tags": [ + "Magazines" + ], + "summary": "Viewer Magazines Thumbnail", + "parameters": [ + { + "type": "string", + "description": "Magazines Thumbnail Name", + "name": "thumbnailName", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines/thumbnail/{id}": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Save Thumbnail of Magazines", + "produces": [ + "application/json" + ], + "tags": [ + "Magazines" + ], + "summary": "Save Thumbnail Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Magazine ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "file", + "description": "Upload thumbnail", + "name": "files", + "in": "formData", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/magazines/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Magazines", + "tags": [ + "Magazines" + ], + "summary": "Get one Magazines", + "parameters": [ + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Magazines", + "tags": [ + "Magazines" + ], + "summary": "Update Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MagazinesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Magazines", + "tags": [ + "Magazines" + ], + "summary": "Delete Magazines", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Magazines ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-menus": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Get all MasterMenus", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "integer", + "name": "moduleId", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentMenuId", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Create MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterMenusCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-menus/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Get one MasterMenus", + "parameters": [ + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Update MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterMenus", + "tags": [ + "MasterMenus" + ], + "summary": "Delete MasterMenus", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterMenus ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-modules": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Get all MasterModules", + "parameters": [ + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Create MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-modules/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Get one MasterModules", + "parameters": [ + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Update MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.MasterModulesUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterModules", + "tags": [ + "MasterModules" + ], + "summary": "Delete MasterModules", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "MasterModules ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/master-statuses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Get all MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Create MasterStatuses", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/master-statuses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Get one MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Update MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete MasterStatuses", + "tags": [ + "Untags" + ], + "summary": "Delete MasterStatuses", + "parameters": [ + { + "type": "integer", + "description": "MasterStatuses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Provinces", + "tags": [ + "Untags" + ], + "summary": "Get all Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Provinces", + "tags": [ + "Untags" + ], + "summary": "Create Provinces", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/provinces/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Provinces", + "tags": [ + "Untags" + ], + "summary": "Get one Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Provinces", + "tags": [ + "Untags" + ], + "summary": "Update Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Provinces", + "tags": [ + "Untags" + ], + "summary": "Delete Provinces", + "parameters": [ + { + "type": "integer", + "description": "Provinces ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/research-journals": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Research Journals for authenticated user", + "tags": [ + "Research Journals" + ], + "summary": "Get all Research Journals", + "parameters": [ + { + "type": "string", + "name": "journalTitle", + "in": "query" + }, + { + "type": "integer", + "name": "publishedYear", + "in": "query" + }, + { + "type": "string", + "name": "publisher", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Create Research Journal", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ResearchJournalsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/research-journals/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Get one Research Journal", + "parameters": [ + { + "type": "integer", + "description": "Research Journal ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Update Research Journal", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Research Journal ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ResearchJournalsUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Research Journal", + "tags": [ + "Research Journals" + ], + "summary": "Delete Research Journal", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Research Journal ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/subscription": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Subscription", + "tags": [ + "Subscription" + ], + "summary": "Get all Subscription", + "parameters": [ + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Subscription", + "tags": [ + "Subscription" + ], + "summary": "Create Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/subscription/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Subscription", + "tags": [ + "Subscription" + ], + "summary": "Get one Subscription", + "parameters": [ + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Subscription", + "tags": [ + "Subscription" + ], + "summary": "update Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.SubscriptionUpdateRequest" + } + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Subscription", + "tags": [ + "Subscription" + ], + "summary": "delete Subscription", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Subscription ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get all UserLevels", + "parameters": [ + { + "type": "integer", + "name": "levelNumber", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "parentLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "provinceId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Create UserLevels", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels/alias/{alias}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "string", + "description": "UserLevels Alias", + "name": "alias", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels/enable-approval": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Enable Approval of Article", + "tags": [ + "UserLevels" + ], + "summary": "EnableApproval Articles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsApprovalRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-levels/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "Get one UserLevels", + "parameters": [ + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "update UserLevels", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLevelsUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserLevels", + "tags": [ + "UserLevels" + ], + "summary": "delete UserLevels", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserLevels ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-accesses": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Get all UserRoleAccesses", + "parameters": [ + { + "type": "boolean", + "name": "isActive", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "menuId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Create UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-role-accesses/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "Get one UserRoleAccesses", + "parameters": [ + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "update UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRoleAccessesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleAccesses", + "tags": [ + "UserRoleAccesses" + ], + "summary": "delete UserRoleAccesses", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserRoleAccesses ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-role-level-details": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Get all UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Create UserRoleLevelDetails", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-role-level-details/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "Get one UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "update UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoleLevelDetails", + "tags": [ + "Task" + ], + "summary": "delete UserRoleLevelDetails", + "parameters": [ + { + "type": "integer", + "description": "UserRoleLevelDetails ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/user-roles": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Get all UserRoles", + "parameters": [ + { + "type": "string", + "name": "code", + "in": "query" + }, + { + "type": "string", + "name": "description", + "in": "query" + }, + { + "type": "string", + "name": "name", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userLevelId", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Create UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/user-roles/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "Get one UserRoles", + "parameters": [ + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "update UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserRolesUpdateRequest" + } + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete UserRoles", + "tags": [ + "UserRoles" + ], + "summary": "delete UserRoles", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "UserRoles ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting all Users", + "tags": [ + "Users" + ], + "summary": "Get all Users", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "string", + "name": "degree", + "in": "query" + }, + { + "type": "string", + "name": "email", + "in": "query" + }, + { + "type": "string", + "name": "fullname", + "in": "query" + }, + { + "type": "string", + "name": "genderType", + "in": "query" + }, + { + "type": "string", + "name": "identityGroup", + "in": "query" + }, + { + "type": "string", + "name": "identityGroupNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityNumber", + "in": "query" + }, + { + "type": "string", + "name": "identityType", + "in": "query" + }, + { + "type": "string", + "name": "phoneNumber", + "in": "query" + }, + { + "type": "integer", + "name": "statusId", + "in": "query" + }, + { + "type": "integer", + "name": "userRoleId", + "in": "query" + }, + { + "type": "string", + "name": "username", + "in": "query" + }, + { + "type": "string", + "name": "workType", + "in": "query" + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for create Users", + "tags": [ + "Users" + ], + "summary": "Create Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/detail/{id}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": [ + "Users" + ], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/email-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Email Validation Users", + "tags": [ + "Users" + ], + "summary": "EmailValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/forgot-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ForgotPassword Users", + "tags": [ + "Users" + ], + "summary": "ForgotPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserForgotPassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/info": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ShowUserInfo", + "tags": [ + "Users" + ], + "summary": "ShowInfo Users", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Login Users", + "tags": [ + "Users" + ], + "summary": "Login Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/otp-request": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpRequest Users", + "tags": [ + "Users" + ], + "summary": "OtpRequest Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/otp-validation": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for OtpValidation Users", + "tags": [ + "Users" + ], + "summary": "OtpValidation Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserOtpValidation" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/pareto-login": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ParetoLogin Users", + "tags": [ + "Users" + ], + "summary": "ParetoLogin Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserLogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/reset-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for ResetPassword Users", + "tags": [ + "Users" + ], + "summary": "ResetPassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserResetPassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/save-password": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for SavePassword Users", + "tags": [ + "Users" + ], + "summary": "SavePassword Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserSavePassword" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/setup-email": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for Setup Email Users", + "tags": [ + "Users" + ], + "summary": "SetupEmail Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UserEmailValidationRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/username/{username}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for getting one Users", + "tags": [ + "Users" + ], + "summary": "Get one Users", + "parameters": [ + { + "type": "string", + "description": "Username", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/{id}": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for update Users", + "tags": [ + "Users" + ], + "summary": "update Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "Bearer \u003cAdd access token here\u003e", + "description": "Insert your access token", + "name": "Authorization", + "in": "header" + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.UsersUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for delete Users", + "tags": [ + "Users" + ], + "summary": "delete Users", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Users ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/work-history": { + "get": { + "description": "API for getting all Work History for specific user", + "tags": [ + "Work History" + ], + "summary": "Get all Work History", + "parameters": [ + { + "type": "string", + "name": "companyName", + "in": "query" + }, + { + "type": "boolean", + "description": "filter for current job (EndDate is null)", + "name": "isCurrent", + "in": "query" + }, + { + "type": "string", + "name": "jobTitle", + "in": "query" + }, + { + "type": "integer", + "name": "userId", + "in": "query", + "required": true + }, + { + "type": "integer", + "name": "count", + "in": "query" + }, + { + "type": "integer", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "name": "nextPage", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "name": "previousPage", + "in": "query" + }, + { + "type": "string", + "name": "sort", + "in": "query" + }, + { + "type": "string", + "name": "sortBy", + "in": "query" + }, + { + "type": "integer", + "name": "totalPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "post": { + "description": "API for create Work History", + "tags": [ + "Work History" + ], + "summary": "Create Work History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WorkHistoryCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/work-history/{id}": { + "get": { + "description": "API for getting one Work History", + "tags": [ + "Work History" + ], + "summary": "Get one Work History", + "parameters": [ + { + "type": "integer", + "description": "Work History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "put": { + "description": "API for update Work History", + "tags": [ + "Work History" + ], + "summary": "Update Work History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Work History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + }, + { + "description": "Required payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.WorkHistoryUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + }, + "delete": { + "description": "API for delete Work History", + "tags": [ + "Work History" + ], + "summary": "Delete Work History", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Csrf-Token", + "name": "X-Csrf-Token", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "Work History ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "User ID", + "name": "userId", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + } + }, + "definitions": { + "controller.PaymentStatusUpdateRequest": { + "type": "object", + "required": [ + "paymentStatus" + ], + "properties": { + "paymentProof": { + "type": "string" + }, + "paymentStatus": { + "type": "string" + }, + "transactionId": { + "type": "string" + } + } + }, + "paginator.Pagination": { + "type": "object", + "properties": { + "count": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "nextPage": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "previousPage": { + "type": "integer" + }, + "sort": { + "type": "string" + }, + "sortBy": { + "type": "string" + }, + "totalPage": { + "type": "integer" + } + } + }, + "request.AIChatMessagesCreateRequest": { + "type": "object", + "required": [ + "content", + "messageType", + "sessionId" + ], + "properties": { + "content": { + "type": "string", + "minLength": 1 + }, + "messageType": { + "type": "string", + "enum": [ + "user", + "assistant" + ] + }, + "sessionId": { + "type": "string" + } + } + }, + "request.AIChatMessagesUpdateRequest": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "minLength": 1 + } + } + }, + "request.AIChatSessionsCreateRequest": { + "type": "object", + "required": [ + "agentId", + "sessionId", + "title" + ], + "properties": { + "agentId": { + "type": "string" + }, + "sessionId": { + "type": "string" + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.AIChatSessionsUpdateRequest": { + "type": "object", + "required": [ + "title" + ], + "properties": { + "isActive": { + "type": "boolean" + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.ActivityLogsCreateRequest": { + "type": "object", + "required": [ + "activityTypeId", + "url" + ], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + }, + "visitorIp": { + "type": "string" + } + } + }, + "request.ActivityLogsUpdateRequest": { + "type": "object", + "required": [ + "activityTypeId", + "id", + "url" + ], + "properties": { + "activityTypeId": { + "type": "integer" + }, + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "userId": { + "type": "integer" + } + } + }, + "request.AdvertisementCreateRequest": { + "type": "object", + "required": [ + "description", + "placement", + "redirectLink", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.AdvertisementUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "placement", + "redirectLink", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "placement": { + "type": "string" + }, + "redirectLink": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleApprovalsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleApprovalsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCategoriesCreateRequest": { + "type": "object", + "required": [ + "description", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "oldCategoryId": { + "type": "integer" + }, + "parentId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCategoriesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "parentId": { + "type": "integer" + }, + "publishedAt": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ArticleCommentsApprovalRequest": { + "type": "object", + "required": [ + "id", + "statusId" + ], + "properties": { + "id": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsCreateRequest": { + "type": "object", + "required": [ + "articleId", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleCommentsUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "message" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "parentId": { + "type": "integer" + } + } + }, + "request.ArticleFilesUpdateRequest": { + "type": "object", + "required": [ + "articleId", + "id", + "isPublish", + "publishedAt", + "statusId" + ], + "properties": { + "articleId": { + "type": "integer" + }, + "fileAlt": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "filePath": { + "type": "string" + }, + "fileThumbnail": { + "type": "string" + }, + "fileUrl": { + "type": "string" + }, + "heightPixel": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "publishedAt": { + "type": "string" + }, + "size": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "widthPixel": { + "type": "string" + } + } + }, + "request.ArticlesCreateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "oldId": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.ArticlesUpdateRequest": { + "type": "object", + "required": [ + "categoryIds", + "description", + "htmlDescription", + "slug", + "tags", + "title", + "typeId" + ], + "properties": { + "aiArticleId": { + "type": "integer" + }, + "categoryIds": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "htmlDescription": { + "type": "string" + }, + "isDraft": { + "type": "boolean" + }, + "isPublish": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + }, + "typeId": { + "type": "integer" + } + } + }, + "request.CampaignDestinationsCreateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "name" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "subType": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "request.CampaignDestinationsUpdateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "name" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "subType": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "request.CampaignTypesCreateRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.CampaignTypesUpdateRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.CampaignsCreateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "title" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "creatorId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "mediaItemSelected": { + "type": "string" + }, + "mediaPromote": { + "type": "boolean" + }, + "mediaTypeSelected": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.CampaignsUpdateRequest": { + "type": "object", + "required": [ + "campaignTypeId", + "title" + ], + "properties": { + "campaignTypeId": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "endDate": { + "type": "string" + }, + "mediaItemSelected": { + "type": "string" + }, + "mediaPromote": { + "type": "boolean" + }, + "mediaTypeSelected": { + "type": "string" + }, + "purpose": { + "type": "string" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.ChatMessageCreateRequest": { + "type": "object", + "required": [ + "chatSessionId", + "message" + ], + "properties": { + "chatSessionId": { + "type": "integer" + }, + "message": { + "type": "string", + "maxLength": 1000, + "minLength": 1 + }, + "messageType": { + "type": "string", + "enum": [ + "text", + "image", + "file", + "user", + "assistant" + ] + } + } + }, + "request.ChatMessageUpdateRequest": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string", + "maxLength": 1000, + "minLength": 1 + } + } + }, + "request.ChatScheduleCreateRequest": { + "type": "object", + "required": [ + "scheduled_at", + "title" + ], + "properties": { + "chat_session_id": { + "description": "Optional - if empty, will create new chat session", + "type": "integer" + }, + "description": { + "type": "string", + "maxLength": 1000 + }, + "duration": { + "description": "15 minutes to 8 hours", + "type": "integer", + "maximum": 480, + "minimum": 15 + }, + "file_ids": { + "description": "Array of file IDs to attach to schedule", + "type": "array", + "items": { + "type": "integer" + } + }, + "scheduled_at": { + "type": "string" + }, + "summary": { + "type": "string", + "maxLength": 2000 + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 3 + } + } + }, + "request.ChatScheduleFileUpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "maxLength": 500 + }, + "file_name": { + "type": "string", + "maxLength": 255 + }, + "file_path": { + "type": "string", + "maxLength": 500 + }, + "file_size": { + "type": "integer", + "minimum": 0 + }, + "file_type": { + "type": "string", + "enum": [ + "article", + "journal", + "video", + "audio", + "document", + "other" + ] + }, + "is_required": { + "type": "boolean" + }, + "mime_type": { + "type": "string", + "maxLength": 100 + }, + "original_name": { + "type": "string", + "maxLength": 255 + } + } + }, + "request.ChatScheduleUpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string", + "maxLength": 1000 + }, + "duration": { + "type": "integer", + "maximum": 480, + "minimum": 15 + }, + "file_ids": { + "description": "Array of file IDs to attach to schedule", + "type": "array", + "items": { + "type": "integer" + } + }, + "scheduled_at": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "scheduled", + "ongoing", + "completed", + "cancelled" + ] + }, + "summary": { + "type": "string", + "maxLength": 2000 + }, + "title": { + "type": "string", + "maxLength": 255, + "minLength": 3 + } + } + }, + "request.ChatSessionCreateRequest": { + "type": "object", + "required": [ + "type", + "userIds" + ], + "properties": { + "name": { + "description": "null for personal chat", + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "type": { + "type": "string", + "enum": [ + "personal", + "group" + ] + }, + "userIds": { + "description": "participants (excluding creator)", + "type": "array", + "minItems": 1, + "items": { + "type": "integer" + } + } + } + }, + "request.ChatSessionUpdateRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.CitiesCreateRequest": { + "type": "object", + "required": [ + "cityName", + "provId" + ], + "properties": { + "cityName": { + "type": "string" + }, + "provId": { + "type": "integer" + } + } + }, + "request.CitiesUpdateRequest": { + "type": "object", + "required": [ + "cityName", + "id", + "provId" + ], + "properties": { + "cityName": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "provId": { + "type": "integer" + } + } + }, + "request.CustomStaticPagesCreateRequest": { + "type": "object", + "required": [ + "htmlBody", + "slug", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.CustomStaticPagesUpdateRequest": { + "type": "object", + "required": [ + "htmlBody", + "id", + "slug", + "title" + ], + "properties": { + "description": { + "type": "string" + }, + "htmlBody": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "request.EbookRatingsCreateRequest": { + "type": "object", + "required": [ + "ebookId", + "purchaseId", + "rating" + ], + "properties": { + "ebookId": { + "type": "integer" + }, + "isAnonymous": { + "type": "boolean" + }, + "purchaseId": { + "type": "integer" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbookRatingsUpdateRequest": { + "type": "object", + "required": [ + "rating" + ], + "properties": { + "isAnonymous": { + "type": "boolean" + }, + "rating": { + "type": "integer", + "maximum": 5, + "minimum": 1 + }, + "review": { + "type": "string" + } + } + }, + "request.EbooksCreateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.EbooksUpdateRequest": { + "type": "object", + "required": [ + "description", + "price", + "slug", + "title" + ], + "properties": { + "category": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublished": { + "type": "boolean" + }, + "isbn": { + "type": "string" + }, + "language": { + "type": "string" + }, + "pageCount": { + "type": "integer" + }, + "price": { + "type": "number", + "minimum": 0 + }, + "publishedYear": { + "type": "integer" + }, + "publisher": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "tags": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.EducationHistoryCreateRequest": { + "type": "object", + "required": [ + "educationLevel", + "graduationYear", + "major", + "schoolName" + ], + "properties": { + "certificateImage": { + "type": "string" + }, + "educationLevel": { + "type": "string", + "maxLength": 100, + "minLength": 2 + }, + "graduationYear": { + "type": "integer", + "maximum": 2030, + "minimum": 1950 + }, + "major": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "schoolName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.EducationHistoryUpdateRequest": { + "type": "object", + "required": [ + "educationLevel", + "graduationYear", + "major", + "schoolName" + ], + "properties": { + "certificateImage": { + "type": "string" + }, + "educationLevel": { + "type": "string", + "maxLength": 100, + "minLength": 2 + }, + "graduationYear": { + "type": "integer", + "maximum": 2030, + "minimum": 1950 + }, + "major": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "schoolName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.FeedbacksCreateRequest": { + "type": "object", + "required": [ + "commentFromEmail", + "commentFromName", + "message" + ], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "request.FeedbacksUpdateRequest": { + "type": "object", + "required": [ + "commentFromEmail", + "commentFromName", + "id", + "message" + ], + "properties": { + "commentFromEmail": { + "type": "string" + }, + "commentFromName": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + } + } + }, + "request.MagazinesCreateRequest": { + "type": "object", + "required": [ + "description", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MagazinesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "statusId", + "title" + ], + "properties": { + "createdById": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublish": { + "type": "boolean" + }, + "pageUrl": { + "type": "string" + }, + "publishedAt": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "thumbnailPath": { + "type": "string" + }, + "thumbnailUrl": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "request.MasterMenusCreateRequest": { + "type": "object", + "required": [ + "description", + "group", + "moduleId", + "name", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "group": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "moduleId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentMenuId": { + "type": "integer" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesCreateRequest": { + "type": "object", + "required": [ + "description", + "name", + "pathUrl", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.MasterModulesUpdateRequest": { + "type": "object", + "required": [ + "description", + "id", + "name", + "pathUrl", + "statusId" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "pathUrl": { + "type": "string" + }, + "statusId": { + "type": "integer" + } + } + }, + "request.ResearchJournalsCreateRequest": { + "type": "object", + "required": [ + "journalTitle", + "journalUrl", + "publisher" + ], + "properties": { + "journalTitle": { + "type": "string", + "maxLength": 500, + "minLength": 2 + }, + "journalUrl": { + "type": "string" + }, + "publishedDate": { + "type": "string" + }, + "publisher": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.ResearchJournalsUpdateRequest": { + "type": "object", + "required": [ + "journalTitle", + "journalUrl", + "publisher" + ], + "properties": { + "journalTitle": { + "type": "string", + "maxLength": 500, + "minLength": 2 + }, + "journalUrl": { + "type": "string" + }, + "publishedDate": { + "type": "string" + }, + "publisher": { + "type": "string", + "maxLength": 255, + "minLength": 2 + } + } + }, + "request.SubscriptionCreateRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + } + } + }, + "request.SubscriptionUpdateRequest": { + "type": "object", + "required": [ + "email", + "id" + ], + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "integer" + } + } + }, + "request.UserEmailValidationRequest": { + "type": "object", + "properties": { + "newEmail": { + "type": "string" + }, + "oldEmail": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserForgotPassword": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + }, + "request.UserLevelsApprovalRequest": { + "type": "object", + "required": [ + "ids", + "isApprovalActive" + ], + "properties": { + "ids": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + } + } + }, + "request.UserLevelsCreateRequest": { + "type": "object", + "required": [ + "aliasName", + "levelNumber", + "name" + ], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLevelsUpdateRequest": { + "type": "object", + "required": [ + "aliasName", + "levelNumber", + "name" + ], + "properties": { + "aliasName": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isApprovalActive": { + "type": "boolean" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parentLevelId": { + "type": "integer" + }, + "provinceId": { + "type": "integer" + } + } + }, + "request.UserLogin": { + "type": "object", + "properties": { + "password": { + "type": "string" + }, + "refreshToken": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserOtpRequest": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "request.UserOtpValidation": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "otpCode": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "request.UserResetPassword": { + "type": "object", + "properties": { + "codeRequest": { + "type": "string" + }, + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, + "request.UserRoleAccessesCreateRequest": { + "type": "object", + "required": [ + "isAdminEnabled", + "isApprovalEnabled", + "isDeleteEnabled", + "isInsertEnabled", + "isUpdateEnabled", + "isViewEnabled", + "menuId" + ], + "properties": { + "isAdminEnabled": { + "type": "boolean" + }, + "isApprovalEnabled": { + "type": "boolean" + }, + "isDeleteEnabled": { + "type": "boolean" + }, + "isInsertEnabled": { + "type": "boolean" + }, + "isUpdateEnabled": { + "type": "boolean" + }, + "isViewEnabled": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + } + } + }, + "request.UserRoleAccessesUpdateRequest": { + "type": "object", + "required": [ + "id", + "isAdminEnabled", + "isApprovalEnabled", + "isDeleteEnabled", + "isInsertEnabled", + "isUpdateEnabled", + "isViewEnabled", + "menuId", + "userRoleId" + ], + "properties": { + "id": { + "type": "integer" + }, + "isAdminEnabled": { + "type": "boolean" + }, + "isApprovalEnabled": { + "type": "boolean" + }, + "isDeleteEnabled": { + "type": "boolean" + }, + "isInsertEnabled": { + "type": "boolean" + }, + "isUpdateEnabled": { + "type": "boolean" + }, + "isViewEnabled": { + "type": "boolean" + }, + "menuId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + } + } + }, + "request.UserRolesCreateRequest": { + "type": "object", + "required": [ + "code", + "description", + "name", + "statusId", + "userLevelIds", + "userRoleAccess" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + }, + "userRoleAccess": { + "type": "array", + "items": { + "$ref": "#/definitions/request.UserRoleAccessesCreateRequest" + } + } + } + }, + "request.UserRolesUpdateRequest": { + "type": "object", + "required": [ + "code", + "description", + "levelNumber", + "name", + "statusId", + "userLevelIds" + ], + "properties": { + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "levelNumber": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelIds": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "request.UserSavePassword": { + "type": "object", + "properties": { + "confirmPassword": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "request.UsersCreateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "password", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "degree": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "lastJobTitle": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "whatsappNumber": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "request.UsersUpdateRequest": { + "type": "object", + "required": [ + "email", + "fullname", + "userLevelId", + "userRoleId", + "username" + ], + "properties": { + "address": { + "type": "string" + }, + "dateOfBirth": { + "type": "string" + }, + "degree": { + "type": "string" + }, + "email": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "genderType": { + "type": "string" + }, + "identityGroup": { + "type": "string" + }, + "identityGroupNumber": { + "type": "string" + }, + "identityNumber": { + "type": "string" + }, + "identityType": { + "type": "string" + }, + "lastEducation": { + "type": "string" + }, + "lastJobTitle": { + "type": "string" + }, + "phoneNumber": { + "type": "string" + }, + "statusId": { + "type": "integer" + }, + "userLevelId": { + "type": "integer" + }, + "userRoleId": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "whatsappNumber": { + "type": "string" + }, + "workType": { + "type": "string" + } + } + }, + "request.WorkHistoryCreateRequest": { + "type": "object", + "required": [ + "companyName", + "jobTitle", + "startDate" + ], + "properties": { + "companyName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "endDate": { + "type": "string" + }, + "jobTitle": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "startDate": { + "type": "string" + } + } + }, + "request.WorkHistoryUpdateRequest": { + "type": "object", + "required": [ + "companyName", + "jobTitle", + "startDate" + ], + "properties": { + "companyName": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "endDate": { + "type": "string" + }, + "jobTitle": { + "type": "string", + "maxLength": 255, + "minLength": 2 + }, + "startDate": { + "type": "string" + } + } + }, + "response.BadRequestError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 400 + }, + "message": { + "type": "string", + "example": "bad request" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.InternalServerError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 500 + }, + "message": { + "type": "string", + "example": "internal server error" + }, + "success": { + "type": "boolean", + "example": false + } + } + }, + "response.Response": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 200 + }, + "data": {}, + "messages": { + "type": "array", + "items": {} + }, + "meta": {}, + "success": { + "type": "boolean", + "example": true + } + } + }, + "response.UnauthorizedError": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "example": 401 + }, + "message": { + "type": "string", + "example": "unauthorized access" + }, + "success": { + "type": "boolean", + "example": false + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml new file mode 100644 index 0000000..e320f75 --- /dev/null +++ b/docs/swagger/swagger.yaml @@ -0,0 +1,11656 @@ +definitions: + controller.PaymentStatusUpdateRequest: + properties: + paymentProof: + type: string + paymentStatus: + type: string + transactionId: + type: string + required: + - paymentStatus + type: object + paginator.Pagination: + properties: + count: + type: integer + limit: + type: integer + nextPage: + type: integer + page: + type: integer + previousPage: + type: integer + sort: + type: string + sortBy: + type: string + totalPage: + type: integer + type: object + request.AIChatMessagesCreateRequest: + properties: + content: + minLength: 1 + type: string + messageType: + enum: + - user + - assistant + type: string + sessionId: + type: string + required: + - content + - messageType + - sessionId + type: object + request.AIChatMessagesUpdateRequest: + properties: + content: + minLength: 1 + type: string + required: + - content + type: object + request.AIChatSessionsCreateRequest: + properties: + agentId: + type: string + sessionId: + type: string + title: + maxLength: 255 + minLength: 2 + type: string + required: + - agentId + - sessionId + - title + type: object + request.AIChatSessionsUpdateRequest: + properties: + isActive: + type: boolean + title: + maxLength: 255 + minLength: 2 + type: string + required: + - title + type: object + request.ActivityLogsCreateRequest: + properties: + activityTypeId: + type: integer + articleId: + type: integer + url: + type: string + userId: + type: integer + visitorIp: + type: string + required: + - activityTypeId + - url + type: object + request.ActivityLogsUpdateRequest: + properties: + activityTypeId: + type: integer + articleId: + type: integer + id: + type: integer + url: + type: string + userId: + type: integer + required: + - activityTypeId + - id + - url + type: object + request.AdvertisementCreateRequest: + properties: + description: + type: string + placement: + type: string + redirectLink: + type: string + title: + type: string + required: + - description + - placement + - redirectLink + - title + type: object + request.AdvertisementUpdateRequest: + properties: + description: + type: string + id: + type: integer + placement: + type: string + redirectLink: + type: string + title: + type: string + required: + - description + - id + - placement + - redirectLink + - title + type: object + request.ArticleApprovalsCreateRequest: + properties: + articleId: + type: integer + message: + type: string + statusId: + type: integer + required: + - articleId + - message + - statusId + type: object + request.ArticleApprovalsUpdateRequest: + properties: + articleId: + type: integer + id: + type: integer + message: + type: string + statusId: + type: integer + required: + - articleId + - id + - message + - statusId + type: object + request.ArticleCategoriesCreateRequest: + properties: + createdById: + type: integer + description: + type: string + oldCategoryId: + type: integer + parentId: + type: integer + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + required: + - description + - statusId + - title + type: object + request.ArticleCategoriesUpdateRequest: + properties: + createdById: + type: integer + description: + type: string + id: + type: integer + isPublish: + type: boolean + parentId: + type: integer + publishedAt: + type: string + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + required: + - description + - id + - statusId + - title + type: object + request.ArticleCommentsApprovalRequest: + properties: + id: + type: integer + statusId: + type: integer + required: + - id + - statusId + type: object + request.ArticleCommentsCreateRequest: + properties: + articleId: + type: integer + isPublic: + type: boolean + message: + type: string + parentId: + type: integer + required: + - articleId + - message + type: object + request.ArticleCommentsUpdateRequest: + properties: + articleId: + type: integer + id: + type: integer + isPublic: + type: boolean + message: + type: string + parentId: + type: integer + required: + - articleId + - id + - message + type: object + request.ArticleFilesUpdateRequest: + properties: + articleId: + type: integer + fileAlt: + type: string + fileName: + type: string + filePath: + type: string + fileThumbnail: + type: string + fileUrl: + type: string + heightPixel: + type: string + id: + type: integer + isPublish: + type: boolean + publishedAt: + type: string + size: + type: string + statusId: + type: integer + widthPixel: + type: string + required: + - articleId + - id + - isPublish + - publishedAt + - statusId + type: object + request.ArticlesCreateRequest: + properties: + aiArticleId: + type: integer + categoryIds: + type: string + createdAt: + type: string + createdById: + type: integer + description: + type: string + htmlDescription: + type: string + isDraft: + type: boolean + isPublish: + type: boolean + oldId: + type: integer + slug: + type: string + tags: + type: string + title: + type: string + typeId: + type: integer + required: + - categoryIds + - description + - htmlDescription + - slug + - tags + - title + - typeId + type: object + request.ArticlesUpdateRequest: + properties: + aiArticleId: + type: integer + categoryIds: + type: string + createdAt: + type: string + createdById: + type: integer + description: + type: string + htmlDescription: + type: string + isDraft: + type: boolean + isPublish: + type: boolean + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + typeId: + type: integer + required: + - categoryIds + - description + - htmlDescription + - slug + - tags + - title + - typeId + type: object + request.CampaignDestinationsCreateRequest: + properties: + campaignTypeId: + type: integer + createdById: + type: integer + description: + type: string + isActive: + type: boolean + name: + type: string + subType: + type: string + url: + type: string + required: + - campaignTypeId + - name + type: object + request.CampaignDestinationsUpdateRequest: + properties: + campaignTypeId: + type: integer + description: + type: string + isActive: + type: boolean + name: + type: string + subType: + type: string + url: + type: string + required: + - campaignTypeId + - name + type: object + request.CampaignTypesCreateRequest: + properties: + createdById: + type: integer + description: + type: string + name: + type: string + required: + - name + type: object + request.CampaignTypesUpdateRequest: + properties: + description: + type: string + name: + type: string + required: + - name + type: object + request.CampaignsCreateRequest: + properties: + campaignTypeId: + type: integer + creatorId: + type: integer + description: + type: string + endDate: + type: string + mediaItemSelected: + type: string + mediaPromote: + type: boolean + mediaTypeSelected: + type: string + purpose: + type: string + startDate: + type: string + status: + type: string + title: + type: string + required: + - campaignTypeId + - title + type: object + request.CampaignsUpdateRequest: + properties: + campaignTypeId: + type: integer + description: + type: string + endDate: + type: string + mediaItemSelected: + type: string + mediaPromote: + type: boolean + mediaTypeSelected: + type: string + purpose: + type: string + startDate: + type: string + status: + type: string + title: + type: string + required: + - campaignTypeId + - title + type: object + request.ChatMessageCreateRequest: + properties: + chatSessionId: + type: integer + message: + maxLength: 1000 + minLength: 1 + type: string + messageType: + enum: + - text + - image + - file + - user + - assistant + type: string + required: + - chatSessionId + - message + type: object + request.ChatMessageUpdateRequest: + properties: + message: + maxLength: 1000 + minLength: 1 + type: string + required: + - message + type: object + request.ChatScheduleCreateRequest: + properties: + chat_session_id: + description: Optional - if empty, will create new chat session + type: integer + description: + maxLength: 1000 + type: string + duration: + description: 15 minutes to 8 hours + maximum: 480 + minimum: 15 + type: integer + file_ids: + description: Array of file IDs to attach to schedule + items: + type: integer + type: array + scheduled_at: + type: string + summary: + maxLength: 2000 + type: string + title: + maxLength: 255 + minLength: 3 + type: string + required: + - scheduled_at + - title + type: object + request.ChatScheduleFileUpdateRequest: + properties: + description: + maxLength: 500 + type: string + file_name: + maxLength: 255 + type: string + file_path: + maxLength: 500 + type: string + file_size: + minimum: 0 + type: integer + file_type: + enum: + - article + - journal + - video + - audio + - document + - other + type: string + is_required: + type: boolean + mime_type: + maxLength: 100 + type: string + original_name: + maxLength: 255 + type: string + type: object + request.ChatScheduleUpdateRequest: + properties: + description: + maxLength: 1000 + type: string + duration: + maximum: 480 + minimum: 15 + type: integer + file_ids: + description: Array of file IDs to attach to schedule + items: + type: integer + type: array + scheduled_at: + type: string + status: + enum: + - scheduled + - ongoing + - completed + - cancelled + type: string + summary: + maxLength: 2000 + type: string + title: + maxLength: 255 + minLength: 3 + type: string + type: object + request.ChatSessionCreateRequest: + properties: + name: + description: null for personal chat + maxLength: 255 + minLength: 2 + type: string + type: + enum: + - personal + - group + type: string + userIds: + description: participants (excluding creator) + items: + type: integer + minItems: 1 + type: array + required: + - type + - userIds + type: object + request.ChatSessionUpdateRequest: + properties: + name: + maxLength: 255 + minLength: 2 + type: string + type: object + request.CitiesCreateRequest: + properties: + cityName: + type: string + provId: + type: integer + required: + - cityName + - provId + type: object + request.CitiesUpdateRequest: + properties: + cityName: + type: string + id: + type: integer + provId: + type: integer + required: + - cityName + - id + - provId + type: object + request.CustomStaticPagesCreateRequest: + properties: + description: + type: string + htmlBody: + type: string + slug: + type: string + title: + type: string + required: + - htmlBody + - slug + - title + type: object + request.CustomStaticPagesUpdateRequest: + properties: + description: + type: string + htmlBody: + type: string + id: + type: integer + slug: + type: string + title: + type: string + updatedAt: + type: string + required: + - htmlBody + - id + - slug + - title + type: object + request.EbookRatingsCreateRequest: + properties: + ebookId: + type: integer + isAnonymous: + type: boolean + purchaseId: + type: integer + rating: + maximum: 5 + minimum: 1 + type: integer + review: + type: string + required: + - ebookId + - purchaseId + - rating + type: object + request.EbookRatingsUpdateRequest: + properties: + isAnonymous: + type: boolean + rating: + maximum: 5 + minimum: 1 + type: integer + review: + type: string + required: + - rating + type: object + request.EbooksCreateRequest: + properties: + category: + type: string + createdAt: + type: string + createdById: + type: integer + description: + type: string + isPublished: + type: boolean + isbn: + type: string + language: + type: string + pageCount: + type: integer + price: + minimum: 0 + type: number + publishedYear: + type: integer + publisher: + type: string + slug: + type: string + tags: + type: string + title: + type: string + required: + - description + - price + - slug + - title + type: object + request.EbooksUpdateRequest: + properties: + category: + type: string + createdAt: + type: string + createdById: + type: integer + description: + type: string + isPublished: + type: boolean + isbn: + type: string + language: + type: string + pageCount: + type: integer + price: + minimum: 0 + type: number + publishedYear: + type: integer + publisher: + type: string + slug: + type: string + statusId: + type: integer + tags: + type: string + title: + type: string + required: + - description + - price + - slug + - title + type: object + request.EducationHistoryCreateRequest: + properties: + certificateImage: + type: string + educationLevel: + maxLength: 100 + minLength: 2 + type: string + graduationYear: + maximum: 2030 + minimum: 1950 + type: integer + major: + maxLength: 255 + minLength: 2 + type: string + schoolName: + maxLength: 255 + minLength: 2 + type: string + required: + - educationLevel + - graduationYear + - major + - schoolName + type: object + request.EducationHistoryUpdateRequest: + properties: + certificateImage: + type: string + educationLevel: + maxLength: 100 + minLength: 2 + type: string + graduationYear: + maximum: 2030 + minimum: 1950 + type: integer + major: + maxLength: 255 + minLength: 2 + type: string + schoolName: + maxLength: 255 + minLength: 2 + type: string + required: + - educationLevel + - graduationYear + - major + - schoolName + type: object + request.FeedbacksCreateRequest: + properties: + commentFromEmail: + type: string + commentFromName: + type: string + message: + type: string + required: + - commentFromEmail + - commentFromName + - message + type: object + request.FeedbacksUpdateRequest: + properties: + commentFromEmail: + type: string + commentFromName: + type: string + id: + type: integer + message: + type: string + required: + - commentFromEmail + - commentFromName + - id + - message + type: object + request.MagazinesCreateRequest: + properties: + createdById: + type: integer + description: + type: string + isPublish: + type: boolean + pageUrl: + type: string + publishedAt: + type: string + statusId: + type: integer + thumbnailPath: + type: string + thumbnailUrl: + type: string + title: + type: string + required: + - description + - statusId + - title + type: object + request.MagazinesUpdateRequest: + properties: + createdById: + type: integer + description: + type: string + id: + type: integer + isPublish: + type: boolean + pageUrl: + type: string + publishedAt: + type: string + statusId: + type: integer + thumbnailPath: + type: string + thumbnailUrl: + type: string + title: + type: string + required: + - description + - id + - statusId + - title + type: object + request.MasterMenusCreateRequest: + properties: + description: + type: string + group: + type: string + icon: + type: string + moduleId: + type: integer + name: + type: string + parentMenuId: + type: integer + statusId: + type: integer + required: + - description + - group + - moduleId + - name + - statusId + type: object + request.MasterModulesCreateRequest: + properties: + description: + type: string + name: + type: string + pathUrl: + type: string + statusId: + type: integer + required: + - description + - name + - pathUrl + - statusId + type: object + request.MasterModulesUpdateRequest: + properties: + description: + type: string + id: + type: integer + name: + type: string + pathUrl: + type: string + statusId: + type: integer + required: + - description + - id + - name + - pathUrl + - statusId + type: object + request.ResearchJournalsCreateRequest: + properties: + journalTitle: + maxLength: 500 + minLength: 2 + type: string + journalUrl: + type: string + publishedDate: + type: string + publisher: + maxLength: 255 + minLength: 2 + type: string + required: + - journalTitle + - journalUrl + - publisher + type: object + request.ResearchJournalsUpdateRequest: + properties: + journalTitle: + maxLength: 500 + minLength: 2 + type: string + journalUrl: + type: string + publishedDate: + type: string + publisher: + maxLength: 255 + minLength: 2 + type: string + required: + - journalTitle + - journalUrl + - publisher + type: object + request.SubscriptionCreateRequest: + properties: + email: + type: string + required: + - email + type: object + request.SubscriptionUpdateRequest: + properties: + email: + type: string + id: + type: integer + required: + - email + - id + type: object + request.UserEmailValidationRequest: + properties: + newEmail: + type: string + oldEmail: + type: string + password: + type: string + username: + type: string + type: object + request.UserForgotPassword: + properties: + username: + type: string + type: object + request.UserLevelsApprovalRequest: + properties: + ids: + type: string + isApprovalActive: + type: boolean + required: + - ids + - isApprovalActive + type: object + request.UserLevelsCreateRequest: + properties: + aliasName: + type: string + group: + type: string + isActive: + type: boolean + isApprovalActive: + type: boolean + levelNumber: + type: integer + name: + type: string + parentLevelId: + type: integer + provinceId: + type: integer + required: + - aliasName + - levelNumber + - name + type: object + request.UserLevelsUpdateRequest: + properties: + aliasName: + type: string + group: + type: string + isApprovalActive: + type: boolean + levelNumber: + type: integer + name: + type: string + parentLevelId: + type: integer + provinceId: + type: integer + required: + - aliasName + - levelNumber + - name + type: object + request.UserLogin: + properties: + password: + type: string + refreshToken: + type: string + username: + type: string + type: object + request.UserOtpRequest: + properties: + email: + type: string + name: + type: string + required: + - email + type: object + request.UserOtpValidation: + properties: + email: + type: string + otpCode: + type: string + username: + type: string + type: object + request.UserResetPassword: + properties: + codeRequest: + type: string + confirmPassword: + type: string + password: + type: string + userId: + type: string + type: object + request.UserRoleAccessesCreateRequest: + properties: + isAdminEnabled: + type: boolean + isApprovalEnabled: + type: boolean + isDeleteEnabled: + type: boolean + isInsertEnabled: + type: boolean + isUpdateEnabled: + type: boolean + isViewEnabled: + type: boolean + menuId: + type: integer + required: + - isAdminEnabled + - isApprovalEnabled + - isDeleteEnabled + - isInsertEnabled + - isUpdateEnabled + - isViewEnabled + - menuId + type: object + request.UserRoleAccessesUpdateRequest: + properties: + id: + type: integer + isAdminEnabled: + type: boolean + isApprovalEnabled: + type: boolean + isDeleteEnabled: + type: boolean + isInsertEnabled: + type: boolean + isUpdateEnabled: + type: boolean + isViewEnabled: + type: boolean + menuId: + type: integer + userRoleId: + type: integer + required: + - id + - isAdminEnabled + - isApprovalEnabled + - isDeleteEnabled + - isInsertEnabled + - isUpdateEnabled + - isViewEnabled + - menuId + - userRoleId + type: object + request.UserRolesCreateRequest: + properties: + code: + type: string + description: + type: string + name: + type: string + statusId: + type: integer + userLevelIds: + items: + type: integer + type: array + userRoleAccess: + items: + $ref: '#/definitions/request.UserRoleAccessesCreateRequest' + type: array + required: + - code + - description + - name + - statusId + - userLevelIds + - userRoleAccess + type: object + request.UserRolesUpdateRequest: + properties: + code: + type: string + description: + type: string + levelNumber: + type: integer + name: + type: string + statusId: + type: integer + userLevelIds: + items: + type: integer + type: array + required: + - code + - description + - levelNumber + - name + - statusId + - userLevelIds + type: object + request.UserSavePassword: + properties: + confirmPassword: + type: string + password: + type: string + type: object + request.UsersCreateRequest: + properties: + address: + type: string + dateOfBirth: + type: string + degree: + type: string + email: + type: string + fullname: + type: string + genderType: + type: string + identityGroup: + type: string + identityGroupNumber: + type: string + identityNumber: + type: string + identityType: + type: string + lastEducation: + type: string + lastJobTitle: + type: string + password: + type: string + phoneNumber: + type: string + userLevelId: + type: integer + userRoleId: + type: integer + username: + type: string + whatsappNumber: + type: string + workType: + type: string + required: + - email + - fullname + - password + - userLevelId + - userRoleId + - username + type: object + request.UsersUpdateRequest: + properties: + address: + type: string + dateOfBirth: + type: string + degree: + type: string + email: + type: string + fullname: + type: string + genderType: + type: string + identityGroup: + type: string + identityGroupNumber: + type: string + identityNumber: + type: string + identityType: + type: string + lastEducation: + type: string + lastJobTitle: + type: string + phoneNumber: + type: string + statusId: + type: integer + userLevelId: + type: integer + userRoleId: + type: integer + username: + type: string + whatsappNumber: + type: string + workType: + type: string + required: + - email + - fullname + - userLevelId + - userRoleId + - username + type: object + request.WorkHistoryCreateRequest: + properties: + companyName: + maxLength: 255 + minLength: 2 + type: string + endDate: + type: string + jobTitle: + maxLength: 255 + minLength: 2 + type: string + startDate: + type: string + required: + - companyName + - jobTitle + - startDate + type: object + request.WorkHistoryUpdateRequest: + properties: + companyName: + maxLength: 255 + minLength: 2 + type: string + endDate: + type: string + jobTitle: + maxLength: 255 + minLength: 2 + type: string + startDate: + type: string + required: + - companyName + - jobTitle + - startDate + type: object + response.BadRequestError: + properties: + code: + example: 400 + type: integer + message: + example: bad request + type: string + success: + example: false + type: boolean + type: object + response.InternalServerError: + properties: + code: + example: 500 + type: integer + message: + example: internal server error + type: string + success: + example: false + type: boolean + type: object + response.Response: + properties: + code: + example: 200 + type: integer + data: {} + messages: + items: {} + type: array + meta: {} + success: + example: true + type: boolean + type: object + response.UnauthorizedError: + properties: + code: + example: 401 + type: integer + message: + example: unauthorized access + type: string + success: + example: false + type: boolean + type: object +info: + contact: {} +paths: + /activity-logs: + get: + description: API for getting all ActivityLogs + parameters: + - in: query + name: activityTypeId + type: integer + - in: query + name: articleId + type: integer + - in: query + name: url + type: string + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all ActivityLogs + tags: + - ActivityLogs + post: + description: API for create ActivityLogs + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ActivityLogsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ActivityLogs + tags: + - ActivityLogs + /activity-logs/{id}: + delete: + description: API for delete ActivityLogs + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ActivityLogs ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete ActivityLogs + tags: + - ActivityLogs + put: + description: API for update ActivityLogs + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ActivityLogsUpdateRequest' + - description: ActivityLogs ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update ActivityLogs + tags: + - ActivityLogs + /activity-logs/detail/{id}: + get: + description: API for getting one ActivityLogs + parameters: + - description: ActivityLogs ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ActivityLogs + tags: + - ActivityLogs + /activity-logs/statistics: + get: + description: API for get activity stats ActivityLogs + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get activity stats ActivityLogs + tags: + - ActivityLogs + /advertisement: + get: + description: API for getting all Advertisement + parameters: + - in: query + name: description + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: placement + type: string + - in: query + name: redirectLink + type: string + - in: query + name: statusId + type: integer + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Advertisement + tags: + - Advertisement + post: + description: API for create Advertisement + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.AdvertisementCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Advertisement + tags: + - Advertisement + /advertisement/{id}: + delete: + description: API for delete Advertisement + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Advertisement ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete Advertisement + tags: + - Advertisement + get: + description: API for getting one Advertisement + parameters: + - description: Advertisement ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Advertisement + tags: + - Advertisement + put: + description: API for update Advertisement + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.AdvertisementUpdateRequest' + - description: Advertisement ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update Advertisement + tags: + - Advertisement + /advertisement/publish/{id}: + put: + description: API for Update Publish Advertisement + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Advertisement ID + in: path + name: id + required: true + type: integer + - description: Advertisement Publish Status + in: query + name: isPublish + required: true + type: boolean + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Publish Advertisement + tags: + - Advertisement + /advertisement/upload/{id}: + post: + description: API for Upload File Advertisement + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload file + in: formData + name: file + required: true + type: file + - description: Advertisement ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Upload Advertisement + tags: + - Advertisement + /advertisement/viewer/{filename}: + get: + description: API for Viewer Advertisement + parameters: + - description: Content File Name + in: path + name: filename + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer Advertisement + tags: + - Advertisement + /ai-chat/logs: + get: + description: API for getting all AI chat logs for authenticated user + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: logType + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user AI chat logs + tags: + - AI Chat + /ai-chat/logs/{id}: + get: + description: API for getting one AI chat log + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Log ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one AI chat log + tags: + - AI Chat + /ai-chat/sessions: + get: + description: API for getting all AI chat sessions for authenticated user + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: isActive + type: boolean + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user AI chat sessions + tags: + - AI Chat + post: + description: API for create AI chat session + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.AIChatSessionsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create AI chat session + tags: + - AI Chat + /ai-chat/sessions/{id}: + delete: + description: API for delete AI chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Session ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete AI chat session + tags: + - AI Chat + get: + description: API for getting one AI chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Session ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one AI chat session + tags: + - AI Chat + put: + description: API for update AI chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - description: Session ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.AIChatSessionsUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update AI chat session + tags: + - AI Chat + /ai-chat/sessions/{id}/messages: + get: + description: API for getting all messages in an AI chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Session ID + in: path + name: id + required: true + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get AI chat session messages + tags: + - AI Chat + /ai-chat/sessions/messages: + post: + description: API for sending a message to an AI chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.AIChatMessagesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Send message to AI chat session + tags: + - AI Chat + /ai-chat/sessions/messages/{messageId}: + delete: + description: API for delete AI chat message + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Message ID + in: path + name: messageId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete AI chat message + tags: + - AI Chat + put: + description: API for update AI chat message + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Message ID + in: path + name: messageId + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.AIChatMessagesUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update AI chat message + tags: + - AI Chat + /article-approvals: + get: + description: API for getting all ArticleApprovals + parameters: + - in: query + name: approvalAtLevel + type: integer + - in: query + name: approvalBy + type: integer + - in: query + name: articleId + type: integer + - in: query + name: message + type: string + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all ArticleApprovals + tags: + - ArticleApprovals + post: + description: API for create ArticleApprovals + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleApprovalsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ArticleApprovals + tags: + - ArticleApprovals + /article-approvals/{id}: + delete: + description: API for delete ArticleApprovals + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ArticleApprovals ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete ArticleApprovals + tags: + - ArticleApprovals + get: + description: API for getting one ArticleApprovals + parameters: + - description: ArticleApprovals ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleApprovals + tags: + - ArticleApprovals + put: + description: API for update ArticleApprovals + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleApprovalsUpdateRequest' + - description: ArticleApprovals ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update ArticleApprovals + tags: + - ArticleApprovals + /article-categories: + get: + description: API for getting all ArticleCategories + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: UserLevelId + type: integer + - in: query + name: UserLevelNumber + type: integer + - in: query + name: description + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: parentId + type: integer + - in: query + name: statusId + type: integer + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all ArticleCategories + tags: + - Article Categories + post: + description: API for create ArticleCategories + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCategoriesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ArticleCategories + tags: + - Article Categories + /article-categories/{id}: + delete: + description: API for delete ArticleCategories + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ArticleCategories ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete ArticleCategories + tags: + - Article Categories + get: + description: API for getting one ArticleCategories + parameters: + - description: ArticleCategories ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleCategories + tags: + - Article Categories + put: + description: API for update ArticleCategories + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCategoriesUpdateRequest' + - description: ArticleCategories ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update ArticleCategories + tags: + - Article Categories + /article-categories/old/{id}: + get: + description: API for getting one ArticleCategories + parameters: + - description: ArticleCategories Old ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleCategories + tags: + - Article Categories + /article-categories/slug/{slug}: + get: + description: API for getting one ArticleCategories + parameters: + - description: ArticleCategories Slug + in: path + name: slug + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleCategories + tags: + - Article Categories + /article-categories/thumbnail/{id}: + post: + description: API for Upload ArticleCategories Thumbnail + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + - description: ArticleCategories ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Upload ArticleCategories Thumbnail + tags: + - Article Categories + /article-categories/thumbnail/viewer/{id}: + get: + description: API for View Thumbnail of ArticleCategories + parameters: + - description: ArticleCategories ID + in: path + name: id + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer ArticleCategories + tags: + - Article Categories + /article-category-details: + get: + description: API for getting all ArticleCategoryDetails + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all ArticleCategoryDetails + tags: + - Untags + post: + description: API for create ArticleCategoryDetails + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Create ArticleCategoryDetails + tags: + - Untags + /article-category-details/{id}: + delete: + description: API for delete ArticleCategoryDetails + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ArticleCategoryDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: delete ArticleCategoryDetails + tags: + - Untags + get: + description: API for getting one ArticleCategoryDetails + parameters: + - description: ArticleCategoryDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one ArticleCategoryDetails + tags: + - Untags + put: + description: API for update ArticleCategoryDetails + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ArticleCategoryDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: update ArticleCategoryDetails + tags: + - Untags + /article-comments: + get: + description: API for getting all ArticleComments + parameters: + - in: query + name: articleId + type: integer + - in: query + name: commentFrom + type: integer + - in: query + name: isPublic + type: boolean + - in: query + name: message + type: string + - in: query + name: parentId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all ArticleComments + tags: + - ArticleComments + post: + description: API for create ArticleComments + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCommentsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ArticleComments + tags: + - ArticleComments + /article-comments/{id}: + delete: + description: API for delete ArticleComments + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ArticleComments ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete ArticleComments + tags: + - ArticleComments + get: + description: API for getting one ArticleComments + parameters: + - description: ArticleComments ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleComments + tags: + - ArticleComments + put: + description: API for update ArticleComments + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCommentsUpdateRequest' + - description: ArticleComments ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update ArticleComments + tags: + - ArticleComments + /article-comments/approval: + post: + description: API for Approval ArticleComments + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleCommentsApprovalRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Approval ArticleComments + tags: + - ArticleComments + /article-files: + get: + description: API for getting all ArticleFiles + parameters: + - in: query + name: articleId + type: integer + - in: query + name: fileName + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all ArticleFiles + tags: + - Article Files + /article-files/{articleId}: + post: + description: API for create ArticleFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload file + in: formData + name: files + required: true + type: file + - description: Article ID + in: path + name: articleId + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Upload ArticleFiles + tags: + - Article Files + /article-files/{id}: + delete: + description: API for delete ArticleFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: ArticleFiles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete ArticleFiles + tags: + - Article Files + get: + description: API for getting one ArticleFiles + parameters: + - description: ArticleFiles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one ArticleFiles + tags: + - Article Files + put: + description: API for update ArticleFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticleFilesUpdateRequest' + - description: ArticleFiles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update ArticleFiles + tags: + - Article Files + /article-files/upload-status/{uploadId}: + get: + description: API for GetUploadStatus ArticleFiles + parameters: + - description: Upload ID of ArticleFiles + in: path + name: uploadId + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: GetUploadStatus ArticleFiles + tags: + - Article Files + /article-files/viewer/{filename}: + get: + description: API for Viewer ArticleFiles + parameters: + - description: Article File Name + in: path + name: filename + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer ArticleFiles + tags: + - Article Files + /articles: + get: + description: API for getting all Articles + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: category + type: string + - in: query + name: categoryId + type: integer + - in: query + name: createdById + type: integer + - in: query + name: description + type: string + - in: query + name: isBanner + type: boolean + - in: query + name: isDraft + type: boolean + - in: query + name: isPublish + type: boolean + - in: query + name: statusId + type: integer + - in: query + name: tags + type: string + - in: query + name: title + type: string + - in: query + name: typeId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Articles + tags: + - Articles + post: + description: API for create Articles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticlesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Articles + tags: + - Articles + /articles/{id}: + delete: + description: API for delete Articles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Articles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Articles + tags: + - Articles + get: + description: API for getting one Articles + parameters: + - description: Articles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Articles + tags: + - Articles + put: + description: API for update Articles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ArticlesUpdateRequest' + - description: Articles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Articles + tags: + - Articles + /articles/banner/{id}: + put: + description: API for Update Banner Articles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Articles ID + in: path + name: id + required: true + type: integer + - description: Articles Banner Status + in: query + name: isBanner + required: true + type: boolean + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Banner Articles + tags: + - Articles + /articles/old-id/{id}: + get: + description: API for getting one Articles + parameters: + - description: Articles Old ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Articles + tags: + - Articles + /articles/publish-scheduling: + post: + description: API for Publish Schedule of Article + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: article id + in: query + name: id + type: integer + - description: publish date + in: query + name: date + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: PublishScheduling Articles + tags: + - Articles + /articles/statistic/monthly: + get: + description: API for ArticleMonthlyStats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: year + in: query + name: year + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: ArticleMonthlyStats Articles + tags: + - Articles + /articles/statistic/summary: + get: + description: API for Summary Stats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: SummaryStats Articles + tags: + - Articles + /articles/statistic/user-levels: + get: + description: API for ArticlePerUserLevelStats of Article + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: start date + in: query + name: startDate + type: string + - description: start date + in: query + name: endDate + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: ArticlePerUserLevelStats Articles + tags: + - Articles + /articles/thumbnail/{id}: + post: + description: API for Save Thumbnail of Articles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + - description: Articles ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Save Thumbnail Articles + tags: + - Articles + /articles/thumbnail/viewer/{thumbnailName}: + get: + description: API for View Thumbnail of Article + parameters: + - description: Articles Thumbnail Name + in: path + name: thumbnailName + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer Articles Thumbnail + tags: + - Articles + /campaign-destinations: + get: + description: API for getting all CampaignDestinations + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: campaignTypeId + type: integer + - in: query + name: isActive + type: boolean + - in: query + name: name + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all CampaignDestinations + tags: + - CampaignDestinations + post: + description: API for create CampaignDestinations + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CampaignDestinationsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create CampaignDestinations + tags: + - CampaignDestinations + /campaign-destinations/{id}: + delete: + description: API for delete CampaignDestinations + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: CampaignDestinations ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete CampaignDestinations + tags: + - CampaignDestinations + get: + description: API for getting one CampaignDestinations + parameters: + - description: CampaignDestinations ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one CampaignDestinations + tags: + - CampaignDestinations + put: + description: API for update CampaignDestinations + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CampaignDestinationsUpdateRequest' + - description: CampaignDestinations ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update CampaignDestinations + tags: + - CampaignDestinations + /campaign-types: + get: + description: API for getting all CampaignTypes + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: name + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all CampaignTypes + tags: + - CampaignTypes + post: + description: API for create CampaignTypes + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CampaignTypesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create CampaignTypes + tags: + - CampaignTypes + /campaign-types/{id}: + delete: + description: API for delete CampaignTypes + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: CampaignTypes ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete CampaignTypes + tags: + - CampaignTypes + get: + description: API for getting one CampaignTypes + parameters: + - description: CampaignTypes ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one CampaignTypes + tags: + - CampaignTypes + put: + description: API for update CampaignTypes + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CampaignTypesUpdateRequest' + - description: CampaignTypes ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update CampaignTypes + tags: + - CampaignTypes + /campaigns: + get: + description: API for getting all Campaigns + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: campaignTypeId + type: integer + - in: query + name: creatorId + type: integer + - in: query + name: status + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Campaigns + tags: + - Campaigns + post: + description: API for create Campaigns + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CampaignsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Campaigns + tags: + - Campaigns + /campaigns/{id}: + delete: + description: API for delete Campaigns + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Campaigns ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Campaigns + tags: + - Campaigns + get: + description: API for getting one Campaigns + parameters: + - description: Campaigns ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Campaigns + tags: + - Campaigns + put: + description: API for update Campaigns + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CampaignsUpdateRequest' + - description: Campaigns ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Campaigns + tags: + - Campaigns + /chat/messages: + get: + description: API for getting all messages in a specific chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: query + name: chatSessionId + required: true + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all chat messages + tags: + - Chat + post: + description: API for creating a new chat message + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatMessageCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create chat message + tags: + - Chat + /chat/messages/{id}: + delete: + description: API for deleting chat message (only sender can delete) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Message ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete chat message + tags: + - Chat + get: + description: API for getting one chat message + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Message ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one chat message + tags: + - Chat + put: + description: API for updating chat message (only sender can update) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Message ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatMessageUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update chat message + tags: + - Chat + /chat/schedule-files: + get: + description: API for getting files for a specific chat schedule + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule ID + in: query + name: chatScheduleId + required: true + type: integer + - description: File type filter + in: query + name: fileType + type: string + - description: Required file filter + in: query + name: isRequired + type: boolean + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get chat schedule files + tags: + - Chat Schedule File + /chat/schedule-files/{chatScheduleId}: + post: + description: API for uploading file for chat schedule + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Upload file + in: formData + name: files + required: true + type: file + - description: Chat Schedule ID + in: path + name: chatScheduleId + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Upload chat schedule file + tags: + - Chat Schedule File + /chat/schedule-files/{id}: + delete: + description: API for deleting chat schedule file + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule File ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete chat schedule file + tags: + - Chat Schedule File + get: + description: API for getting one chat schedule file + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule File ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one chat schedule file + tags: + - Chat Schedule File + put: + description: API for updating chat schedule file + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule File ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatScheduleFileUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update chat schedule file + tags: + - Chat Schedule File + /chat/schedule-files/viewer/{filename}: + get: + description: API for viewing chat schedule file + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule File Name + in: path + name: filename + required: true + type: string + responses: + "200": + description: OK + schema: + type: file + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: View chat schedule file + tags: + - Chat Schedule File + /chat/schedules: + get: + description: API for getting all chat schedules for authenticated user + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: query + name: chatSessionId + type: string + - description: Schedule status (scheduled, ongoing, completed, cancelled) + in: query + name: status + type: string + - description: Created by user ID + in: query + name: createdBy + type: string + - description: Date from (YYYY-MM-DD) + in: query + name: dateFrom + type: string + - description: Date to (YYYY-MM-DD) + in: query + name: dateTo + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all chat schedules + tags: + - Chat Schedule + post: + description: API for creating a new chat schedule + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatScheduleCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create chat schedule + tags: + - Chat Schedule + /chat/schedules/{id}: + delete: + description: API for deleting chat schedule (only creator can delete) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete chat schedule + tags: + - Chat Schedule + get: + description: API for getting one chat schedule + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one chat schedule + tags: + - Chat Schedule + put: + description: API for updating chat schedule (only creator can update) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatScheduleUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update chat schedule + tags: + - Chat Schedule + /chat/schedules/{id}/reminder: + post: + description: API for sending reminder for a chat schedule (only creator can + send) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Schedule ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Send schedule reminder + tags: + - Chat Schedule + /chat/schedules/status/{status}: + get: + description: API for getting chat schedules by status + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Schedule status (scheduled, ongoing, completed, cancelled) + in: path + name: status + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get schedules by status + tags: + - Chat Schedule + /chat/schedules/upcoming: + get: + description: API for getting upcoming chat schedules for authenticated user + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - default: 10 + description: Limit number of results + in: query + name: limit + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get upcoming schedules + tags: + - Chat Schedule + /chat/sessions: + get: + description: API for getting all chat sessions for authenticated user + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat type (personal or group) + in: query + name: type + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all chat sessions + tags: + - Chat + post: + description: API for creating a new chat session (personal or group) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatSessionCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create chat session + tags: + - Chat + /chat/sessions/{chatSessionId}/participants: + delete: + description: API for removing a participant from a chat session (creator can + remove anyone, user can remove themselves) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: path + name: chatSessionId + required: true + type: integer + - description: Participant User ID + in: query + name: participantUserId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Remove participant from chat session + tags: + - Chat + post: + description: API for adding a participant to a chat session (only creator can + add) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: path + name: chatSessionId + required: true + type: integer + - description: Participant User ID + in: query + name: participantUserId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Add participant to chat session + tags: + - Chat + /chat/sessions/{id}: + delete: + description: API for deleting chat session (only creator can delete) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete chat session + tags: + - Chat + get: + description: API for getting one chat session + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one chat session + tags: + - Chat + put: + description: API for updating chat session (only creator can update) + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Chat Session ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ChatSessionUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update chat session + tags: + - Chat + /cities: + get: + description: API for getting all Cities + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all Cities + tags: + - Untags + post: + description: API for create Cities + parameters: + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CitiesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Create Cities + tags: + - Untags + /cities/{id}: + delete: + description: API for delete Cities + parameters: + - description: Cities ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Delete Cities + tags: + - Untags + get: + description: API for getting one Cities + parameters: + - description: Cities ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one Cities + tags: + - Untags + put: + consumes: + - application/json + description: API for update Cities + parameters: + - description: Cities ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CitiesUpdateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update Cities + tags: + - Untags + /custom-static-pages: + get: + description: API for getting all CustomStaticPages + parameters: + - in: query + name: description + type: string + - in: query + name: htmlBody + type: string + - in: query + name: slug + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all CustomStaticPages + tags: + - CustomStaticPages + post: + description: API for create CustomStaticPages + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CustomStaticPagesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create CustomStaticPages + tags: + - CustomStaticPages + /custom-static-pages/{id}: + delete: + description: API for delete CustomStaticPages + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: CustomStaticPages ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete CustomStaticPages + tags: + - CustomStaticPages + get: + description: API for getting one CustomStaticPages + parameters: + - description: CustomStaticPages ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one CustomStaticPages + tags: + - CustomStaticPages + put: + description: API for update CustomStaticPages + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CustomStaticPagesUpdateRequest' + - description: CustomStaticPages ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update CustomStaticPages + tags: + - CustomStaticPages + /custom-static-pages/slug/{slug}: + get: + description: API for getting one CustomStaticPages + parameters: + - description: CustomStaticPages Slug + in: path + name: slug + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one CustomStaticPages + tags: + - CustomStaticPages + /districts: + get: + description: API for getting all Districts + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all Districts + tags: + - Untags + post: + description: API for create Districts + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Create Districts + tags: + - Untags + /districts/{id}: + delete: + description: API for delete Districts + parameters: + - description: Districts ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Delete Districts + tags: + - Untags + get: + description: API for getting one Districts + parameters: + - description: Districts ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one Districts + tags: + - Untags + put: + description: API for update Districts + parameters: + - description: Districts ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update Districts + tags: + - Untags + /ebook-ratings: + get: + description: API for getting all Ebook Ratings + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: ebookId + type: integer + - in: query + name: isVerified + type: boolean + - in: query + name: rating + type: integer + - in: query + name: statusId + type: integer + - in: query + name: userId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Ebook Ratings + tags: + - Ebook Ratings + post: + description: API for creating ebook rating + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbookRatingsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create ebook rating + tags: + - Ebook Ratings + /ebook-ratings/{id}: + delete: + description: API for deleting ebook rating + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Rating ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete ebook rating + tags: + - Ebook Ratings + put: + description: API for updating ebook rating + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbookRatingsUpdateRequest' + - description: Rating ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update ebook rating + tags: + - Ebook Ratings + /ebook-ratings/ebook/{id}: + get: + description: API for getting ratings by ebook ID + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get ratings by ebook ID + tags: + - Ebook Ratings + /ebook-ratings/stats/{id}: + get: + description: API for getting ebook rating statistics + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get ebook rating statistics + tags: + - Ebook Ratings + /ebook-ratings/summary/{id}: + get: + description: API for getting ebook rating summary with stats and recent reviews + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get ebook rating summary + tags: + - Ebook Ratings + /ebook-ratings/user: + get: + description: API for getting user ratings + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user ratings + tags: + - Ebook Ratings + /ebooks: + get: + description: API for getting all Ebooks + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: authorId + type: integer + - in: query + name: category + type: string + - in: query + name: description + type: string + - in: query + name: isPublished + type: boolean + - in: query + name: maxPrice + type: number + - in: query + name: minPrice + type: number + - in: query + name: statusId + type: integer + - in: query + name: tags + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Ebooks + tags: + - Ebooks + post: + description: API for create Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbooksCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Ebook + tags: + - Ebooks + /ebooks/{id}: + delete: + description: API for delete Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Ebook + tags: + - Ebooks + get: + description: API for getting one Ebook + parameters: + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Ebook + tags: + - Ebooks + put: + description: API for update Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EbooksUpdateRequest' + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Ebook + tags: + - Ebooks + /ebooks/download/{id}: + get: + description: API for Download PDF of Ebook + parameters: + - description: Ebook ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Download PDF Ebook + tags: + - Ebooks + /ebooks/pdf/{id}: + post: + description: API for Save PDF File of Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload PDF file + in: formData + name: file + required: true + type: file + - description: Ebook ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Save PDF File Ebook + tags: + - Ebooks + /ebooks/purchase/{ebookId}: + post: + description: API for purchasing ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + - description: Payment Method + in: query + name: paymentMethod + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Purchase ebook + tags: + - Ebook Purchase + /ebooks/purchases: + get: + description: API for getting user purchases + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user purchases + tags: + - Ebook Purchase + /ebooks/purchases/{id}: + get: + description: API for getting purchase by ID + parameters: + - description: Purchase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get purchase by ID + tags: + - Ebook Purchase + /ebooks/purchases/{id}/confirm: + put: + description: API for confirming payment + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Purchase ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Confirm payment + tags: + - Ebook Purchase + /ebooks/purchases/{id}/payment: + put: + description: API for updating payment status + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Purchase ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/controller.PaymentStatusUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update payment status + tags: + - Ebook Purchase + /ebooks/slug/{slug}: + get: + description: API for getting one Ebook by slug + parameters: + - description: Ebook Slug + in: path + name: slug + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Ebook by slug + tags: + - Ebooks + /ebooks/statistic/summary: + get: + description: API for Summary Stats of Ebook + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: SummaryStats Ebook + tags: + - Ebooks + /ebooks/thumbnail/{id}: + post: + description: API for Save Thumbnail of Ebook + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + - description: Ebook ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Save Thumbnail Ebook + tags: + - Ebooks + /ebooks/wishlist: + get: + description: API for getting user wishlist + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get user wishlist + tags: + - Ebook Wishlist + /ebooks/wishlist/{ebookId}: + delete: + description: API for removing ebook from wishlist + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Remove ebook from wishlist + tags: + - Ebook Wishlist + post: + description: API for adding ebook to wishlist + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Add ebook to wishlist + tags: + - Ebook Wishlist + /ebooks/wishlist/check/{ebookId}: + get: + description: API for checking if ebook is in wishlist + parameters: + - description: Ebook ID + in: path + name: ebookId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Check if ebook is in wishlist + tags: + - Ebook Wishlist + /education-history: + get: + description: API for getting all Education History for specific user + parameters: + - in: query + name: educationLevel + type: string + - in: query + name: graduationYear + type: integer + - in: query + name: major + type: string + - in: query + name: schoolName + type: string + - in: query + name: userId + required: true + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Get all Education History + tags: + - Education History + post: + description: API for create Education History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: User ID + in: query + name: userId + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EducationHistoryCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Create Education History + tags: + - Education History + /education-history/{id}: + delete: + description: API for delete Education History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Education History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Delete Education History + tags: + - Education History + get: + description: API for getting one Education History + parameters: + - description: Education History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Get one Education History + tags: + - Education History + put: + description: API for update Education History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Education History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.EducationHistoryUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Update Education History + tags: + - Education History + /education-history/{id}/certificate: + post: + description: API for upload certificate image for Education History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Education History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + - description: Certificate image file + in: formData + name: certificate + required: true + type: file + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Upload Certificate for Education History + tags: + - Education History + /feedbacks: + get: + description: API for getting all Feedbacks + parameters: + - in: query + name: commentFromEmail + type: string + - in: query + name: commentFromName + type: string + - in: query + name: endDate + type: string + - in: query + name: message + type: string + - in: query + name: startDate + type: string + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Feedbacks + tags: + - Feedbacks + post: + description: API for create Feedbacks + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.FeedbacksCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Feedbacks + tags: + - Feedbacks + /feedbacks/{id}: + delete: + description: API for delete Feedbacks + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Feedbacks ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete Feedbacks + tags: + - Feedbacks + get: + description: API for getting one Feedbacks + parameters: + - description: Feedbacks ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Feedbacks + tags: + - Feedbacks + put: + description: API for update Feedbacks + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.FeedbacksUpdateRequest' + - description: Feedbacks ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update Feedbacks + tags: + - Feedbacks + /feedbacks/statistic/monthly: + get: + description: API for FeedbackMonthlyStats of Feedbacks + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: year + in: query + name: year + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: FeedbackMonthlyStats Feedbacks + tags: + - Feedbacks + /magazine-files: + get: + description: API for getting all MagazineFiles + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all MagazineFiles + tags: + - Magazine Files + /magazine-files/{id}: + delete: + description: API for delete MagazineFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: MagazineFiles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete MagazineFiles + tags: + - Magazine Files + get: + description: API for getting one MagazineFiles + parameters: + - description: MagazineFiles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one MagazineFiles + tags: + - Magazine Files + put: + description: API for update MagazineFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: MagazineFiles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update MagazineFiles + tags: + - Magazine Files + /magazine-files/{magazineId}: + post: + description: API for create MagazineFiles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Upload file + in: formData + name: files + required: true + type: file + - description: Magazine file title + in: formData + name: title + required: true + type: string + - description: Magazine file description + in: formData + name: description + required: true + type: string + - description: Magazine ID + in: path + name: magazineId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create MagazineFiles + tags: + - Magazine Files + /magazine-files/viewer/{filename}: + get: + description: API for create MagazineFiles + parameters: + - description: Magazine File Name + in: path + name: filename + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create MagazineFiles + tags: + - Magazine Files + /magazines: + get: + description: API for getting all Magazines + parameters: + - in: query + name: createdById + type: integer + - in: query + name: description + type: string + - in: query + name: isPublish + type: boolean + - in: query + name: pageUrl + type: string + - in: query + name: statusId + type: integer + - in: query + name: thumbnailPath + type: string + - in: query + name: thumbnailUrl + type: string + - in: query + name: title + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Magazines + tags: + - Magazines + post: + description: API for create Magazines + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.MagazinesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Magazines + tags: + - Magazines + /magazines/{id}: + delete: + description: API for delete Magazines + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Magazines ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Magazines + tags: + - Magazines + get: + description: API for getting one Magazines + parameters: + - description: Magazines ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Magazines + tags: + - Magazines + put: + description: API for update Magazines + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Magazines ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.MagazinesUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Magazines + tags: + - Magazines + /magazines/thumbnail/{id}: + post: + description: API for Save Thumbnail of Magazines + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Magazine ID + in: path + name: id + required: true + type: integer + - description: Upload thumbnail + in: formData + name: files + required: true + type: file + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Save Thumbnail Magazines + tags: + - Magazines + /magazines/thumbnail/viewer/{thumbnailName}: + get: + description: API for View Thumbnail of Magazines + parameters: + - description: Magazines Thumbnail Name + in: path + name: thumbnailName + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer Magazines Thumbnail + tags: + - Magazines + /master-menus: + get: + description: API for getting all MasterMenus + parameters: + - in: query + name: description + type: string + - in: query + name: moduleId + type: integer + - in: query + name: name + type: string + - in: query + name: parentMenuId + type: integer + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all MasterMenus + tags: + - MasterMenus + post: + description: API for create MasterMenus + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.MasterMenusCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create MasterMenus + tags: + - MasterMenus + /master-menus/{id}: + delete: + description: API for delete MasterMenus + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: MasterMenus ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete MasterMenus + tags: + - MasterMenus + get: + description: API for getting one MasterMenus + parameters: + - description: MasterMenus ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one MasterMenus + tags: + - MasterMenus + put: + description: API for update MasterMenus + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: MasterMenus ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update MasterMenus + tags: + - MasterMenus + /master-modules: + get: + description: API for getting all MasterModules + parameters: + - in: query + name: description + type: string + - in: query + name: name + type: string + - in: query + name: statusId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all MasterModules + tags: + - MasterModules + post: + description: API for create MasterModules + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.MasterModulesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create MasterModules + tags: + - MasterModules + /master-modules/{id}: + delete: + description: API for delete MasterModules + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: MasterModules ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete MasterModules + tags: + - MasterModules + get: + description: API for getting one MasterModules + parameters: + - description: MasterModules ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one MasterModules + tags: + - MasterModules + put: + description: API for update MasterModules + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: MasterModules ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.MasterModulesUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update MasterModules + tags: + - MasterModules + /master-statuses: + get: + description: API for getting all MasterStatuses + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all MasterStatuses + tags: + - Untags + post: + description: API for create MasterStatuses + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Create MasterStatuses + tags: + - Untags + /master-statuses/{id}: + delete: + description: API for delete MasterStatuses + parameters: + - description: MasterStatuses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Delete MasterStatuses + tags: + - Untags + get: + description: API for getting one MasterStatuses + parameters: + - description: MasterStatuses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one MasterStatuses + tags: + - Untags + put: + description: API for update MasterStatuses + parameters: + - description: MasterStatuses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update MasterStatuses + tags: + - Untags + /provinces: + get: + description: API for getting all Provinces + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all Provinces + tags: + - Untags + post: + description: API for create Provinces + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Create Provinces + tags: + - Untags + /provinces/{id}: + delete: + description: API for delete Provinces + parameters: + - description: Provinces ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Delete Provinces + tags: + - Untags + get: + description: API for getting one Provinces + parameters: + - description: Provinces ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one Provinces + tags: + - Untags + put: + description: API for update Provinces + parameters: + - description: Provinces ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Update Provinces + tags: + - Untags + /research-journals: + get: + description: API for getting all Research Journals for authenticated user + parameters: + - in: query + name: journalTitle + type: string + - in: query + name: publishedYear + type: integer + - in: query + name: publisher + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Research Journals + tags: + - Research Journals + post: + description: API for create Research Journal + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ResearchJournalsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Research Journal + tags: + - Research Journals + /research-journals/{id}: + delete: + description: API for delete Research Journal + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Research Journal ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Delete Research Journal + tags: + - Research Journals + get: + description: API for getting one Research Journal + parameters: + - description: Research Journal ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Research Journal + tags: + - Research Journals + put: + description: API for update Research Journal + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Research Journal ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.ResearchJournalsUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Update Research Journal + tags: + - Research Journals + /subscription: + get: + description: API for getting all Subscription + parameters: + - in: query + name: email + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Subscription + tags: + - Subscription + post: + description: API for create Subscription + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.SubscriptionCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Subscription + tags: + - Subscription + /subscription/{id}: + delete: + description: API for delete Subscription + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Subscription ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete Subscription + tags: + - Subscription + get: + description: API for getting one Subscription + parameters: + - description: Subscription ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Subscription + tags: + - Subscription + put: + description: API for update Subscription + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.SubscriptionUpdateRequest' + - description: Subscription ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update Subscription + tags: + - Subscription + /user-levels: + get: + description: API for getting all UserLevels + parameters: + - in: query + name: levelNumber + type: integer + - in: query + name: name + type: string + - in: query + name: parentLevelId + type: integer + - in: query + name: provinceId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all UserLevels + tags: + - UserLevels + post: + description: API for create UserLevels + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserLevelsCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create UserLevels + tags: + - UserLevels + /user-levels/{id}: + delete: + description: API for delete UserLevels + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: UserLevels ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: delete UserLevels + tags: + - UserLevels + get: + description: API for getting one UserLevels + parameters: + - description: UserLevels ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one UserLevels + tags: + - UserLevels + put: + description: API for update UserLevels + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserLevelsUpdateRequest' + - description: UserLevels ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update UserLevels + tags: + - UserLevels + /user-levels/alias/{alias}: + get: + description: API for getting one UserLevels + parameters: + - description: UserLevels Alias + in: path + name: alias + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one UserLevels + tags: + - UserLevels + /user-levels/enable-approval: + post: + description: API for Enable Approval of Article + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserLevelsApprovalRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: EnableApproval Articles + tags: + - UserLevels + /user-role-accesses: + get: + description: API for getting all UserRoleAccesses + parameters: + - in: query + name: isActive + required: true + type: boolean + - in: query + name: menuId + required: true + type: integer + - in: query + name: userRoleId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all UserRoleAccesses + tags: + - UserRoleAccesses + post: + description: API for create UserRoleAccesses + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserRoleAccessesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create UserRoleAccesses + tags: + - UserRoleAccesses + /user-role-accesses/{id}: + delete: + description: API for delete UserRoleAccesses + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: UserRoleAccesses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete UserRoleAccesses + tags: + - UserRoleAccesses + get: + description: API for getting one UserRoleAccesses + parameters: + - description: UserRoleAccesses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one UserRoleAccesses + tags: + - UserRoleAccesses + put: + description: API for update UserRoleAccesses + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserRoleAccessesUpdateRequest' + - description: UserRoleAccesses ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update UserRoleAccesses + tags: + - UserRoleAccesses + /user-role-level-details: + get: + description: API for getting all UserRoleLevelDetails + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get all UserRoleLevelDetails + tags: + - Task + post: + description: API for create UserRoleLevelDetails + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Create UserRoleLevelDetails + tags: + - Task + /user-role-level-details/{id}: + delete: + description: API for delete UserRoleLevelDetails + parameters: + - description: UserRoleLevelDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: delete UserRoleLevelDetails + tags: + - Task + get: + description: API for getting one UserRoleLevelDetails + parameters: + - description: UserRoleLevelDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: Get one UserRoleLevelDetails + tags: + - Task + put: + description: API for update UserRoleLevelDetails + parameters: + - description: UserRoleLevelDetails ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.Response' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.Response' + security: + - Bearer: [] + summary: update UserRoleLevelDetails + tags: + - Task + /user-roles: + get: + description: API for getting all UserRoles + parameters: + - in: query + name: code + type: string + - in: query + name: description + type: string + - in: query + name: name + type: string + - in: query + name: statusId + type: integer + - in: query + name: userLevelId + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all UserRoles + tags: + - UserRoles + post: + description: API for create UserRoles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserRolesCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create UserRoles + tags: + - UserRoles + /user-roles/{id}: + delete: + description: API for delete UserRoles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: UserRoles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete UserRoles + tags: + - UserRoles + get: + description: API for getting one UserRoles + parameters: + - description: UserRoles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one UserRoles + tags: + - UserRoles + put: + description: API for update UserRoles + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserRolesUpdateRequest' + - description: UserRoles ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update UserRoles + tags: + - UserRoles + /users: + get: + description: API for getting all Users + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - in: query + name: degree + type: string + - in: query + name: email + type: string + - in: query + name: fullname + type: string + - in: query + name: genderType + type: string + - in: query + name: identityGroup + type: string + - in: query + name: identityGroupNumber + type: string + - in: query + name: identityNumber + type: string + - in: query + name: identityType + type: string + - in: query + name: phoneNumber + type: string + - in: query + name: statusId + type: integer + - in: query + name: userRoleId + type: integer + - in: query + name: username + type: string + - in: query + name: workType + type: string + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get all Users + tags: + - Users + post: + description: API for create Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UsersCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Create Users + tags: + - Users + /users/{id}: + delete: + description: API for delete Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Users ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: delete Users + tags: + - Users + put: + description: API for update Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Users ID + in: path + name: id + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UsersUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: update Users + tags: + - Users + /users/detail/{id}: + get: + description: API for getting one Users + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Users ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Users + tags: + - Users + /users/email-validation: + post: + description: API for Email Validation Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserEmailValidationRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: EmailValidation Users + tags: + - Users + /users/forgot-password: + post: + description: API for ForgotPassword Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserForgotPassword' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: ForgotPassword Users + tags: + - Users + /users/info: + get: + description: API for ShowUserInfo + parameters: + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: ShowInfo Users + tags: + - Users + /users/login: + post: + description: API for Login Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserLogin' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Login Users + tags: + - Users + /users/otp-request: + post: + description: API for OtpRequest Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserOtpRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: OtpRequest Users + tags: + - Users + /users/otp-validation: + post: + description: API for OtpValidation Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserOtpValidation' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: OtpValidation Users + tags: + - Users + /users/pareto-login: + post: + description: API for ParetoLogin Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserLogin' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: ParetoLogin Users + tags: + - Users + /users/reset-password: + post: + description: API for ResetPassword Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserResetPassword' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: ResetPassword Users + tags: + - Users + /users/save-password: + post: + description: API for SavePassword Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - default: Bearer + description: Insert your access token + in: header + name: Authorization + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserSavePassword' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: SavePassword Users + tags: + - Users + /users/setup-email: + post: + description: API for Setup Email Users + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.UserEmailValidationRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: SetupEmail Users + tags: + - Users + /users/username/{username}: + get: + description: API for getting one Users + parameters: + - description: Username + in: path + name: username + required: true + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Get one Users + tags: + - Users + /work-history: + get: + description: API for getting all Work History for specific user + parameters: + - in: query + name: companyName + type: string + - description: filter for current job (EndDate is null) + in: query + name: isCurrent + type: boolean + - in: query + name: jobTitle + type: string + - in: query + name: userId + required: true + type: integer + - in: query + name: count + type: integer + - in: query + name: limit + type: integer + - in: query + name: nextPage + type: integer + - in: query + name: page + type: integer + - in: query + name: previousPage + type: integer + - in: query + name: sort + type: string + - in: query + name: sortBy + type: string + - in: query + name: totalPage + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Get all Work History + tags: + - Work History + post: + description: API for create Work History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: User ID + in: query + name: userId + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.WorkHistoryCreateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Create Work History + tags: + - Work History + /work-history/{id}: + delete: + description: API for delete Work History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Work History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Delete Work History + tags: + - Work History + get: + description: API for getting one Work History + parameters: + - description: Work History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Get one Work History + tags: + - Work History + put: + description: API for update Work History + parameters: + - description: Insert the X-Csrf-Token + in: header + name: X-Csrf-Token + required: true + type: string + - description: Work History ID + in: path + name: id + required: true + type: integer + - description: User ID + in: query + name: userId + required: true + type: integer + - description: Required payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.WorkHistoryUpdateRequest' + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Update Work History + tags: + - Work History +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6c79ec8 --- /dev/null +++ b/go.mod @@ -0,0 +1,80 @@ +module jaecoo-be + +go 1.21.6 + +require ( + aidanwoods.dev/go-paseto v1.5.3 + github.com/Nerzal/gocloak/v13 v13.9.0 + github.com/arsmn/fiber-swagger/v2 v2.31.1 + github.com/efectn/fx-zerolog v1.1.0 + github.com/go-co-op/gocron v1.37.0 + github.com/go-playground/locales v0.14.1 + github.com/go-playground/universal-translator v0.18.1 + github.com/go-playground/validator/v10 v10.17.0 + github.com/gofiber/fiber/v2 v2.52.4 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/minio/minio-go/v7 v7.0.68 + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/rs/zerolog v1.31.0 + github.com/swaggo/swag v1.16.3 + github.com/wneessen/go-mail v0.6.1 + go.uber.org/fx v1.20.1 + gorm.io/driver/postgres v1.5.4 + gorm.io/gorm v1.25.6 +) + +require ( + aidanwoods.dev/go-result v0.1.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.20.3 // indirect + github.com/go-openapi/jsonreference v0.20.5 // indirect + github.com/go-openapi/spec v0.20.15 // indirect + github.com/go-openapi/swag v0.22.10 // indirect + github.com/go-resty/resty/v2 v2.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.4.3 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/philhofer/fwd v1.1.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/segmentio/ksuid v1.0.4 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/tinylib/msgp v1.1.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.52.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/dig v1.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..418901d --- /dev/null +++ b/go.sum @@ -0,0 +1,326 @@ +aidanwoods.dev/go-paseto v1.5.3 h1:y3pRY9MLWBhfO9VuCN0Bkyxa7Xmkt5coipYJfaOZgOs= +aidanwoods.dev/go-paseto v1.5.3/go.mod h1://T4uDrCXnzls7pKeCXaQ/zC3xv0KtgGMk4wnlOAHSs= +aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= +aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Nerzal/gocloak/v13 v13.9.0 h1:YWsJsdM5b0yhM2Ba3MLydiOlujkBry4TtdzfIzSVZhw= +github.com/Nerzal/gocloak/v13 v13.9.0/go.mod h1:YYuDcXZ7K2zKECyVP7pPqjKxx2AzYSpKDj8d6GuyM10= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/arsmn/fiber-swagger/v2 v2.31.1 h1:VmX+flXiGGNqLX3loMEEzL3BMOZFSPwBEWR04GA6Mco= +github.com/arsmn/fiber-swagger/v2 v2.31.1/go.mod h1:ZHhMprtB3M6jd2mleG03lPGhHH0lk9u3PtfWS1cBhMA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/efectn/fx-zerolog v1.1.0 h1:n/DYCo53t/mXhL6OasOI/4+JQCYa2doc1G3ogvTGoRY= +github.com/efectn/fx-zerolog v1.1.0/go.mod h1:j7ixjXFvkky0z4s7kX0Dz8O/D+E0TQo9uG+GHJijeqQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk= +github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI= +github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.15 h1:8bDcVxF607pTh9NpPwgsH4J5Uhh5mV5XoWnkurdiY+U= +github.com/go-openapi/spec v0.20.15/go.mod h1:o0upgqg5uYFG7O5mADrDVmSG3Wa6y6OLhwiCqQ+sTv4= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= +github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= +github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.68 h1:hTqSIfLlpXaKuNy4baAp4Jjy2sqZEN9hRxD0M4aOfrQ= +github.com/minio/minio-go/v7 v7.0.68/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= +github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= +github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= +github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/wneessen/go-mail v0.6.1 h1:cDGqlGuEEhdILRe53VFzmM9WBk8Xh/QMvbO0oxrNJB4= +github.com/wneessen/go-mail v0.6.1/go.mod h1:G702XlFhzHV0Z4w9j2VsH5K9dJDvj0hx+yOOp1oX9vc= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI= +go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU= +go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk= +go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= +go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= +gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= +gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A= +gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/hl.exe b/hl.exe new file mode 100644 index 0000000..2957141 Binary files /dev/null and b/hl.exe differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..450bd81 --- /dev/null +++ b/main.go @@ -0,0 +1,120 @@ +package main + +import ( + "campaign-pool-be/app/database" + "campaign-pool-be/app/middleware" + "campaign-pool-be/app/module/activity_logs" + "campaign-pool-be/app/module/advertisement" + "campaign-pool-be/app/module/ai_chat" + "campaign-pool-be/app/module/article_approvals" + "campaign-pool-be/app/module/article_categories" + "campaign-pool-be/app/module/article_category_details" + "campaign-pool-be/app/module/article_comments" + "campaign-pool-be/app/module/article_files" + "campaign-pool-be/app/module/articles" + "campaign-pool-be/app/module/campaign_destinations" + "campaign-pool-be/app/module/campaign_files" + "campaign-pool-be/app/module/campaign_types" + "campaign-pool-be/app/module/campaigns" + "campaign-pool-be/app/module/chat" + "campaign-pool-be/app/module/cities" + "campaign-pool-be/app/module/custom_static_pages" + "campaign-pool-be/app/module/districts" + "campaign-pool-be/app/module/ebooks" + "campaign-pool-be/app/module/education_history" + "campaign-pool-be/app/module/feedbacks" + "campaign-pool-be/app/module/magazine_files" + "campaign-pool-be/app/module/magazines" + "campaign-pool-be/app/module/master_menus" + "campaign-pool-be/app/module/master_modules" + "campaign-pool-be/app/module/provinces" + "campaign-pool-be/app/module/research_journals" + "campaign-pool-be/app/module/subscription" + "campaign-pool-be/app/module/user_levels" + "campaign-pool-be/app/module/user_role_accesses" + "campaign-pool-be/app/module/user_role_level_details" + "campaign-pool-be/app/module/user_roles" + "campaign-pool-be/app/module/users" + "campaign-pool-be/app/module/work_history" + "campaign-pool-be/app/router" + "campaign-pool-be/config/config" + "campaign-pool-be/config/logger" + "campaign-pool-be/config/webserver" + "time" + + fxzerolog "github.com/efectn/fx-zerolog" + "go.uber.org/fx" +) + +func main() { + + fx.New( + /* provide patterns */ + // config + fx.Provide(config.NewConfig), + // logging + fx.Provide(logger.NewLogger), + // fiber + fx.Provide(webserver.NewFiber), + // database + fx.Provide(database.NewDatabase), + // middleware + fx.Provide(middleware.NewMiddleware), + // data storage + fx.Provide(config.NewMinio), + // user management + fx.Provide(config.NewKeycloakConfig), + // router + fx.Provide(router.NewRouter), + // smtp config + fx.Provide(config.NewSmtpConfig), + + // provide modules + activity_logs.NewActivityLogsModule, + advertisement.NewAdvertisementModule, + ai_chat.NewAIChatModule, + article_categories.NewArticleCategoriesModule, + article_category_details.NewArticleCategoryDetailsModule, + article_files.NewArticleFilesModule, + article_approvals.NewArticleApprovalsModule, + articles.NewArticlesModule, + article_comments.NewArticleCommentsModule, + chat.NewChatModule, + cities.NewCitiesModule, + custom_static_pages.NewCustomStaticPagesModule, + districts.NewDistrictsModule, + ebooks.NewEbooksModule, + feedbacks.NewFeedbacksModule, + magazines.NewMagazinesModule, + magazine_files.NewMagazineFilesModule, + master_menus.NewMasterMenusModule, + master_modules.NewMasterModulesModule, + provinces.NewProvincesModule, + subscription.NewSubscriptionModule, + user_levels.NewUserLevelsModule, + user_roles.NewUserRolesModule, + user_role_accesses.NewUserRoleAccessesModule, + users.NewUsersModule, + user_role_level_details.NewUserRoleLevelDetailsModule, + education_history.NewEducationHistoryModule, + work_history.NewWorkHistoryModule, + research_journals.NewResearchJournalsModule, + + // Campaign modules + campaign_types.NewCampaignTypesModule, + campaign_destinations.NewCampaignDestinationsModule, + campaigns.NewCampaignsModule, + campaign_files.NewCampaignFilesModule, + + // start aplication + fx.Invoke(webserver.Start), + + // start scheduling + fx.Invoke(webserver.RunScheduling), + + // define logger + fx.WithLogger(fxzerolog.Init()), + + fx.StartTimeout(600*time.Second), + ).Run() +} diff --git a/plan/development-plan.md b/plan/development-plan.md new file mode 100644 index 0000000..d31d8f7 --- /dev/null +++ b/plan/development-plan.md @@ -0,0 +1,147 @@ +✅ 1. Tabel: users +Untuk menyimpan user aplikasi. + +Field Type Notes +id (PK) uuid +name varchar(150) +email varchar(150) unique +password_hash text +role_id (FK) uuid +status enum(active, inactive) default active +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 2. Tabel: role +Field Type +id (PK) uuid +name varchar(100) +description text +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 3. Tabel: campaign_type + +Jenis campaign umum (misalnya: artikel, short video, banner digital, publikasi medsos) + +Field Type +id (PK) uuid +name varchar(150) +description text +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 4. Tabel: campaign_destination + +Tujuan publikasi spesifik sesuai media/platform. + +Contoh: + +Website → news section + +Instagram → feed, reels, story + +Videotron → LED Baliho Jl Gatot Subroto + +Twitter/X → timeline + +Field Type Notes +id (PK) uuid +campaign_type_id (FK) uuid untuk mengelompokkan +sub_type varchar(100) IG Feed, Stories, TikTok, Videotron Mandiri Tower +name varchar(150) nama tujuan +description text +url varchar(255) opsional +is_active boolean default true +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 5. Tabel: campaign + +Tabel utama campaign/artikel/post. + +Field Type Notes +id (PK) uuid +title varchar(255) tambahan +campaign_type_id (FK) uuid +start_date date +end_date date +media_type_selected varchar(100) ex: web, medsos, videotron +media_item_selected uuid / json saran: bisa FK ke destination atau JSON array +purpose text +media_promote boolean yes/no +description text +creator_id (FK) uuid user pembuat +status enum(draft, waiting_approval, approved, rejected, published, archived) workflow +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 6. Tabel: campaign_file + +Untuk file atau URL yang digunakan dalam campaign. + +Field Type +id (PK) uuid +campaign_id (FK) uuid +type enum(url, file) +file_url text +external_url text +is_draft boolean +is_publish boolean +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 7. Tabel: campaign_destination_relation + +Relasi campaign → media tujuan + +Campaign bisa di-post ke banyak tempat sekaligus. + +Field Type +id (PK) uuid +campaign_id (FK) uuid +destination_id (FK) uuid +scheduled_at timestamp +publish_status enum(pending, success, failed) +publish_response text +created_at timestamp +updated_at timestamp + +------------------------------------------- + +✅ 8. Tabel: campaign_approval + +Untuk workflow persetujuan. + +Field Type +id (PK) uuid +campaign_id (FK) uuid +approver_id (FK) uuid +status enum(pending, approved, rejected) +notes text +created_at timestamp + +------------------------------------------- + +✅ 9. Tabel: campaign_chat + +Chat internal antar user di setiap campaign (komentar, diskusi revisi). + +Field Type +id (PK) uuid +campaign_id (FK) uuid +sender_id (FK) uuid +message text +attachment_url text +created_at timestamp \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..1c60de2 --- /dev/null +++ b/readme.md @@ -0,0 +1,7 @@ +``` +go get -v ./... +go mod vendor -v + +swag init -g main.go --output docs/swagger +go run main.go +``` \ No newline at end of file diff --git a/storage/ascii_art.txt b/storage/ascii_art.txt new file mode 100644 index 0000000..040b6c0 --- /dev/null +++ b/storage/ascii_art.txt @@ -0,0 +1,9 @@ + +______ _ _ _____ _ _ +| ___(_) | / ___| | | | +| |_ _| |__ ___ _ __ \ `--.| |_ __ _ _ __| |_ ___ _ __ +| _| | | '_ \ / _ \ '__| `--. \ __/ _` | '__| __/ _ \ '__| +| | | | |_) | __/ | /\__/ / || (_| | | | || __/ | +\_| |_|_.__/ \___|_| \____/ \__\__,_|_| \__\___|_| + + diff --git a/storage/private.go b/storage/private.go new file mode 100644 index 0000000..c4af254 --- /dev/null +++ b/storage/private.go @@ -0,0 +1,8 @@ +package storage + +import ( + "embed" +) + +//go:embed private/* +var Private embed.FS diff --git a/storage/private/example.html b/storage/private/example.html new file mode 100644 index 0000000..f8f360d --- /dev/null +++ b/storage/private/example.html @@ -0,0 +1 @@ +Example html file for private storage. \ No newline at end of file diff --git a/storage/public/example.txt b/storage/public/example.txt new file mode 100644 index 0000000..15e7931 --- /dev/null +++ b/storage/public/example.txt @@ -0,0 +1 @@ +Example txt file for public storage. \ No newline at end of file diff --git a/utils/index.utils.go b/utils/index.utils.go new file mode 100644 index 0000000..4d891a2 --- /dev/null +++ b/utils/index.utils.go @@ -0,0 +1,17 @@ +package utils + +import "github.com/gofiber/fiber/v2" + +func IsEnabled(key bool) func(c *fiber.Ctx) bool { + if key { + return nil + } + + return func(c *fiber.Ctx) bool { return true } +} + +func CsrfErrorHandler(c *fiber.Ctx, err error) error { + return c.Status(fiber.StatusForbidden).JSON(fiber.Map{ + "error": "CSRF protection: " + err.Error(), + }) +} diff --git a/utils/paginator/index.paginator.go b/utils/paginator/index.paginator.go new file mode 100644 index 0000000..9b7823a --- /dev/null +++ b/utils/paginator/index.paginator.go @@ -0,0 +1,72 @@ +package paginator + +import ( + "math" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +const ( + defaultLimit = 10 + defaultSort = "desc" + defaultSortBy = "id" +) + +type Pagination struct { + Limit int `json:"limit,omitempty"` + Offset int `json:"-"` + Page int `json:"page,omitempty"` + NextPage int `json:"nextPage,omitempty"` + PreviousPage int `json:"previousPage,omitempty"` + Count int64 `json:"count,omitempty"` + TotalPage int `json:"totalPage,omitempty"` + Sort string `json:"sort,omitempty"` + SortBy string `json:"sortBy,omitempty"` +} + +func Paging(p *Pagination) *Pagination { + p.TotalPage = int(math.Ceil(float64(p.Count) / float64(p.Limit))) + if p.Page > 1 { + p.PreviousPage = p.Page - 1 + } else { + p.PreviousPage = p.Page + } + if p.Page == p.TotalPage { + p.NextPage = p.Page + } else { + p.NextPage = p.Page + 1 + } + return p +} + +func Paginate(c *fiber.Ctx) (*Pagination, error) { + limit, err := strconv.Atoi(c.Query("limit")) + if err != nil { + limit = defaultLimit + } + page, err := strconv.Atoi(c.Query("page")) + if err != nil { + page = 1 + } + sort := c.Query("sort") + if sort == "" { + sort = defaultSort + } + sortBy := c.Query("sortBy") + if sortBy == "" { + sortBy = defaultSortBy + } + p := &Pagination{ + Limit: limit, + Page: page, + Sort: sort, + SortBy: sortBy, + } + if p.Page == 0 { + p.Page = 1 + } + + p.Offset = (p.Page - 1) * p.Limit + return p, nil +} diff --git a/utils/response/error.response.go b/utils/response/error.response.go new file mode 100644 index 0000000..7c831bb --- /dev/null +++ b/utils/response/error.response.go @@ -0,0 +1,19 @@ +package response + +type BadRequestError struct { + Success bool `json:"success" example:"false"` + Code int `json:"code" example:"400"` + Message string `json:"message" example:"bad request"` +} + +type UnauthorizedError struct { + Success bool `json:"success" example:"false"` + Code int `json:"code" example:"401"` + Message string `json:"message" example:"unauthorized access"` +} + +type InternalServerError struct { + Success bool `json:"success" example:"false"` + Code int `json:"code" example:"500"` + Message string `json:"message" example:"internal server error"` +} diff --git a/utils/response/index.response.go b/utils/response/index.response.go new file mode 100644 index 0000000..d236019 --- /dev/null +++ b/utils/response/index.response.go @@ -0,0 +1,103 @@ +package response + +import ( + val "campaign-pool-be/utils/validator" + "fmt" + "strings" + + validator "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog/log" +) + +// Alias for any slice +type Messages = []any + +type Error struct { + Success bool `json:"success"` + Code int `json:"code"` + Message any `json:"message"` +} + +// error makes it compatible with the error interface +func (e *Error) Error() string { + return fmt.Sprint(e.Message) +} + +// A struct to return normal response +type Response struct { + Success bool `json:"success" example:"true"` + Code int `json:"code" example:"200"` + Messages Messages `json:"messages"` + Data any `json:"data,omitempty"` + Meta any `json:"meta,omitempty"` +} + +// nothiing to describe this fucking variable +var IsProduction bool + +// Default error handler +var ErrorHandler = func(c *fiber.Ctx, err error) error { + resp := Response{ + Code: fiber.StatusInternalServerError, + } + + // handle errors + if c, ok := err.(validator.ValidationErrors); ok { + resp.Code = fiber.StatusUnprocessableEntity + resp.Messages = Messages{removeTopStruct(c.Translate(val.Trans))} + } else if c, ok := err.(*fiber.Error); ok { + resp.Code = c.Code + resp.Messages = Messages{c.Message} + } else if c, ok := err.(*Error); ok { + resp.Code = c.Code + resp.Messages = Messages{c.Message} + + // for ent and other errors + if resp.Messages == nil { + resp.Messages = Messages{err} + } + } else { + resp.Messages = Messages{err.Error()} + } + + if !IsProduction { + log.Error().Err(err).Msg("From: Fiber's error handler") + } + + return Resp(c, resp) +} + +// function to return pretty json response +func Resp(c *fiber.Ctx, resp Response) error { + // set status + if resp.Code == 0 { + resp.Code = fiber.StatusOK + } + c.Status(resp.Code) + // return response + return c.JSON(resp) +} + +// remove unecessary fields from validator message +func removeTopStruct(fields map[string]string) map[string]string { + res := map[string]string{} + + for field, msg := range fields { + stripStruct := field[strings.Index(field, ".")+1:] + res[stripStruct] = msg + } + + return res +} + +func Unauthorized() *Response { + return &Response{ + Success: false, + Code: 401, + Data: nil, + Messages: Messages{ + "Unauthorized", + }, + } +} diff --git a/utils/service/string.service.go b/utils/service/string.service.go new file mode 100644 index 0000000..d753f93 --- /dev/null +++ b/utils/service/string.service.go @@ -0,0 +1,64 @@ +package service + +import ( + "crypto/rand" + "encoding/json" + "math/big" + "strings" + "unicode" +) + +func IsNumeric(str string) bool { + for _, char := range str { + if !unicode.IsDigit(char) { + return false + } + } + return true +} + +func MakeSlug(s string) string { + // Convert to lowercase + s = strings.ToLower(s) + + // Replace spaces with hyphens + s = strings.ReplaceAll(s, " ", "-") + + // Remove non-alphanumeric characters + return strings.Map(func(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' { + return r + } + return -1 + }, s) +} + +func GenerateNumericCode(codeLength int) (string, error) { + const digits = "0123456789" + result := make([]byte, codeLength) + + for i := 0; i < codeLength; i++ { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(digits)))) + if err != nil { + return "", err + } + result[i] = digits[num.Int64()] + } + + return string(result), nil +} + +func StructToMap(obj interface{}) (map[string]interface{}, error) { + var result map[string]interface{} + jsonData, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + err = json.Unmarshal(jsonData, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/utils/service/user_utils.service.go b/utils/service/user_utils.service.go new file mode 100644 index 0000000..ec88826 --- /dev/null +++ b/utils/service/user_utils.service.go @@ -0,0 +1,43 @@ +package service + +import ( + "campaign-pool-be/app/database/entity/users" + "campaign-pool-be/app/module/users/repository" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/rs/zerolog" +) + +func GetUserInfo(log zerolog.Logger, repo repository.UsersRepository, bearerToken string) *users.Users { + tokenString := strings.TrimPrefix(bearerToken, "Bearer ") + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return nil + } + claims := token.Claims.(jwt.MapClaims) + sub := claims["sub"].(string) + + user, err := repo.FindByKeycloakId(sub) + if err != nil { + return nil + } + + log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:GetUserInfo", "UserInfo:GetUserInfo"). + Interface("payload", user).Msg("") + + return user +} + +func GetUserId(bearerToken string) *string { + tokenString := strings.TrimPrefix(bearerToken, "Bearer ") + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return nil + } + claims := token.Claims.(jwt.MapClaims) + sub := claims["sub"].(string) + return &sub +} diff --git a/utils/validator/index.validator.go b/utils/validator/index.validator.go new file mode 100644 index 0000000..d68aafd --- /dev/null +++ b/utils/validator/index.validator.go @@ -0,0 +1,63 @@ +package response + +import ( + "github.com/go-playground/locales/en" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + ent "github.com/go-playground/validator/v10/translations/en" + "github.com/gofiber/fiber/v2" + "github.com/rs/zerolog/log" + "reflect" +) + +var ( + validate *validator.Validate + uni *ut.UniversalTranslator + Trans ut.Translator +) + +func init() { + validate = validator.New() + + uni = ut.New(en.New()) + Trans, _ = uni.GetTranslator("en") + + if err := ent.RegisterDefaultTranslations(validate, Trans); err != nil && !fiber.IsChild() { + log.Panic().Err(err).Msg("") + } +} + +func ValidateStruct(input any) error { + return validate.Struct(input) +} + +func ParseBody(c *fiber.Ctx, body any) error { + if err := c.BodyParser(body); err != nil { + return err + } + + return nil +} + +func ParseAndValidate(c *fiber.Ctx, body any) error { + v := reflect.ValueOf(body) + + switch v.Kind() { + case reflect.Ptr: + err := ParseBody(c, body) + if err != nil { + return err + } + + return ValidateStruct(v.Elem().Interface()) + case reflect.Struct: + err := ParseBody(c, &body) + if err != nil { + return err + } + + return ValidateStruct(v) + default: + return nil + } +}