diff --git a/app/database/entity/notifications.entity.go b/app/database/entity/notifications.entity.go new file mode 100644 index 0000000..acc0794 --- /dev/null +++ b/app/database/entity/notifications.entity.go @@ -0,0 +1,22 @@ +package entity + +import "time" + +type Notifications struct { + ID uint64 `gorm:"primaryKey;autoIncrement;column:id" json:"id"` + + SentTo int `gorm:"column:sent_to;not null;default:0" json:"sentTo"` + SendBy int `gorm:"column:send_by;not null;default:0" json:"sendBy"` + SendByName string `gorm:"column:send_by_name;type:varchar(150);not null;default:''" json:"sendByName"` + Message string `gorm:"column:message;type:text;not null;default:''" json:"message"` + + IsRead bool `gorm:"column:is_read;not null;default:false" json:"isRead"` + IsActive bool `gorm:"column:is_active;not null;default:true" json:"isActive"` + + CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"createdAt"` + UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime" json:"updatedAt"` +} + +func (Notifications) TableName() string { + return "notifications" +} \ No newline at end of file diff --git a/app/module/notifications/controller/controller.go b/app/module/notifications/controller/controller.go new file mode 100644 index 0000000..a546fc2 --- /dev/null +++ b/app/module/notifications/controller/controller.go @@ -0,0 +1,15 @@ +package controller + +import "narasi-ahli-be/app/module/notifications/service" + +type Controller struct { + Notifications NotificationsController +} + +func NewController( + NotificationsService service.NotificationsService, +) *Controller { + return &Controller{ + Notifications: NewNotificationsController(NotificationsService), + } +} diff --git a/app/module/notifications/controller/notifications.controller.go b/app/module/notifications/controller/notifications.controller.go new file mode 100644 index 0000000..56f1fde --- /dev/null +++ b/app/module/notifications/controller/notifications.controller.go @@ -0,0 +1,163 @@ +package controller + +import ( + "narasi-ahli-be/app/module/notifications/request" + "narasi-ahli-be/app/module/notifications/service" + "strconv" + + "github.com/gofiber/fiber/v2" + + utilRes "narasi-ahli-be/utils/response" + utilVal "narasi-ahli-be/utils/validator" +) + +type notificationsController struct { + notificationsService service.NotificationsService +} + +type NotificationsController interface { + Create(c *fiber.Ctx) error + ListBySentTo(c *fiber.Ctx) error + MarkRead(c *fiber.Ctx) error + Delete(c *fiber.Ctx) error +} + +func NewNotificationsController(notificationsService service.NotificationsService) NotificationsController { + return ¬ificationsController{ + notificationsService: notificationsService, + } +} + +// Create Notification +// @Summary Create Notification +// @Description API untuk membuat notifikasi dan mengirim ke user berdasarkan sentTo +// @Tags Notifications +// @Security Bearer +// @Accept json +// @Produce json +// @Param payload body request.CreateNotificationRequest true "Create Notification 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 /notifications [post] +func (_i *notificationsController) Create(c *fiber.Ctx) error { + req := new(request.CreateNotificationRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + data, err := _i.notificationsService.Create(req.SentTo, req.SendBy, req.SendByName, req.Message) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Notification successfully created"}, + Data: data, + }) +} + +// List Notification By SentTo +// @Summary List Notification By SentTo +// @Description API untuk mengambil list notifikasi berdasarkan penerima (sentTo) +// @Tags Notifications +// @Security Bearer +// @Param sentTo path int true "SentTo (Receiver User ID)" +// @Param page query int false "Page" default(1) +// @Param limit query int false "Limit" default(10) +// @Param isRead query bool false "Filter isRead (true/false)" +// @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 /notifications/{sentTo} [get] +func (_i *notificationsController) ListBySentTo(c *fiber.Ctx) error { + sentTo, err := strconv.Atoi(c.Params("sentTo")) + if err != nil { + return err + } + + page, _ := strconv.Atoi(c.Query("page", "1")) + limit, _ := strconv.Atoi(c.Query("limit", "10")) + + // optional filter isRead + var isRead *bool + if c.Query("isRead") != "" { + val := c.QueryBool("isRead", false) + isRead = &val + } + + data, total, err := _i.notificationsService.ListBySentTo(sentTo, page, limit, isRead) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Notifications list successfully retrieved"}, + Data: fiber.Map{ + "total": total, + "page": page, + "limit": limit, + "data": data, + }, + }) +} + +// Mark Read Notification +// @Summary Mark Read Notification +// @Description API untuk mengubah status notifikasi menjadi sudah dibaca +// @Tags Notifications +// @Security Bearer +// @Param id path int true "Notification 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 /notifications/{id}/read [put] +func (_i *notificationsController) MarkRead(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.notificationsService.MarkRead(uint64(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Notification successfully marked as read"}, + }) +} + +// Soft Delete Notification +// @Summary Soft Delete Notification +// @Description API untuk soft delete notifikasi (isActive=false) +// @Tags Notifications +// @Security Bearer +// @Param id path int true "Notification 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 /notifications/{id} [delete] +func (_i *notificationsController) Delete(c *fiber.Ctx) error { + id, err := strconv.ParseUint(c.Params("id"), 10, 0) + if err != nil { + return err + } + + err = _i.notificationsService.Delete(uint64(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Messages: utilRes.Messages{"Notification successfully deleted"}, + }) +} diff --git a/app/module/notifications/notifications.module.go b/app/module/notifications/notifications.module.go new file mode 100644 index 0000000..ae7992d --- /dev/null +++ b/app/module/notifications/notifications.module.go @@ -0,0 +1,51 @@ +package notifications + +import ( + "narasi-ahli-be/app/module/notifications/controller" + "narasi-ahli-be/app/module/notifications/repository" + "narasi-ahli-be/app/module/notifications/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +// struct of NotificationRouter +type NotificationRouter struct { + App fiber.Router + Controller *controller.Controller +} + +// register bulky of Notifications module +var NewNotificationsModule = fx.Options( + // register repository + fx.Provide(repository.NewNotificationsRepository), + + // register service + fx.Provide(service.NewNotificationsService), + + // register controller + fx.Provide(controller.NewController), + + // register router + fx.Provide(NewNotificationRouter), +) + +// init NotificationRouter +func NewNotificationRouter(fiber *fiber.App, controller *controller.Controller) *NotificationRouter { + return &NotificationRouter{ + App: fiber, + Controller: controller, + } +} + +// register routes of Notifications module +func (_i *NotificationRouter) RegisterNotificationRoutes() { + notificationsController := _i.Controller.Notifications + + _i.App.Route("/notifications", func(router fiber.Router) { + router.Post("/", notificationsController.Create) + router.Get("/:sentTo", notificationsController.ListBySentTo) + router.Put("/:id/read", notificationsController.MarkRead) + router.Delete("/:id", notificationsController.Delete) + }) +} diff --git a/app/module/notifications/repository/notifications.repository.go b/app/module/notifications/repository/notifications.repository.go new file mode 100644 index 0000000..81ec246 --- /dev/null +++ b/app/module/notifications/repository/notifications.repository.go @@ -0,0 +1,85 @@ +package repository + +import ( + "narasi-ahli-be/app/database" + "narasi-ahli-be/app/database/entity" +) + +type notificationsRepository struct { + DB *database.Database +} + +type NotificationsRepository interface { + Create(data *entity.Notifications) error + FindById(id uint64) (*entity.Notifications, error) + ListBySentTo(sentTo int, page int, limit int, isRead *bool) ([]entity.Notifications, int64, error) + MarkRead(id uint64) error + Delete(id uint64) error +} + +func NewNotificationsRepository(db *database.Database) NotificationsRepository { + return ¬ificationsRepository{ + DB: db, + } +} + +func (_i *notificationsRepository) Create(data *entity.Notifications) error { + return _i.DB.DB.Create(data).Error +} + +func (_i *notificationsRepository) FindById(id uint64) (*entity.Notifications, error) { + var notif entity.Notifications + err := _i.DB.DB.Where("id = ?", id).First(¬if).Error + if err != nil { + return nil, err + } + return ¬if, nil +} + +func (_i *notificationsRepository) ListBySentTo(sentTo int, page int, limit int, isRead *bool) ([]entity.Notifications, int64, error) { + var data []entity.Notifications + var total int64 + + if page <= 0 { + page = 1 + } + if limit <= 0 { + limit = 10 + } + + q := _i.DB.DB.Model(&entity.Notifications{}). + Where("sent_to = ?", sentTo). + Where("is_active = ?", true) + + if isRead != nil { + q = q.Where("is_read = ?", *isRead) + } + + // count + if err := q.Count(&total).Error; err != nil { + return nil, 0, err + } + + // pagination + offset := (page - 1) * limit + if err := q.Order("created_at DESC"). + Limit(limit). + Offset(offset). + Find(&data).Error; err != nil { + return nil, 0, err + } + + return data, total, nil +} + +func (_i *notificationsRepository) MarkRead(id uint64) error { + return _i.DB.DB.Model(&entity.Notifications{}). + Where("id = ? AND is_active = true", id). + Update("is_read", true).Error +} + +func (_i *notificationsRepository) Delete(id uint64) error { + return _i.DB.DB.Model(&entity.Notifications{}). + Where("id = ?", id). + Update("is_active", false).Error +} diff --git a/app/module/notifications/request/notifications.request.go b/app/module/notifications/request/notifications.request.go new file mode 100644 index 0000000..4996908 --- /dev/null +++ b/app/module/notifications/request/notifications.request.go @@ -0,0 +1,15 @@ +package request + +type CreateNotificationRequest struct { + SentTo int `json:"sentTo" validate:"required"` + SendBy int `json:"sendBy" validate:"required"` + SendByName string `json:"sendByName" validate:"required"` + Message string `json:"message" validate:"required"` +} + +type ListNotificationQuery struct { + Page int `query:"page"` + Limit int `query:"limit"` + IsRead *bool `query:"isRead"` + IsActive *bool `query:"isActive"` +} diff --git a/app/module/notifications/response/notifications.response.go b/app/module/notifications/response/notifications.response.go new file mode 100644 index 0000000..7031e90 --- /dev/null +++ b/app/module/notifications/response/notifications.response.go @@ -0,0 +1,15 @@ +package response + +import "time" + +type NotificationResponse struct { + ID uint64 `json:"id"` + SentTo int `json:"sentTo"` + SendBy int `json:"sendBy"` + SendByName string `json:"sendByName"` + Message string `json:"message"` + IsRead bool `json:"isRead"` + IsActive bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} \ No newline at end of file diff --git a/app/module/notifications/service/notifications.service.go b/app/module/notifications/service/notifications.service.go new file mode 100644 index 0000000..0db9b15 --- /dev/null +++ b/app/module/notifications/service/notifications.service.go @@ -0,0 +1,107 @@ +package service + +import ( + "errors" + "narasi-ahli-be/app/database/entity" + "narasi-ahli-be/app/module/notifications/repository" + "narasi-ahli-be/app/module/notifications/response" +) + +type notificationsService struct { + Repo repository.NotificationsRepository +} + +func NewNotificationsService(repo repository.NotificationsRepository) NotificationsService { + return ¬ificationsService{Repo: repo} +} +type NotificationsService interface { + Create(sentTo int, sendBy int, sendByName string, message string) (*response.NotificationResponse, error) + ListBySentTo(sentTo int, page int, limit int, isRead *bool) ([]response.NotificationResponse, int64, error) + MarkRead(id uint64) error + Delete(id uint64) error +} + +func (s *notificationsService) Create(sentTo int, sendBy int, sendByName string, message string) (*response.NotificationResponse, error) { + if sentTo <= 0 { + return nil, errors.New("sentTo is required") + } + if sendBy <= 0 { + return nil, errors.New("sendBy is required") + } + if sendByName == "" { + return nil, errors.New("sendByName is required") + } + if message == "" { + return nil, errors.New("message is required") + } + + data := entity.Notifications{ + SentTo: sentTo, + SendBy: sendBy, + SendByName: sendByName, + Message: message, + IsRead: false, + IsActive: true, + } + + if err := s.Repo.Create(&data); err != nil { + return nil, err + } + + return &response.NotificationResponse{ + ID: data.ID, + SentTo: data.SentTo, + SendBy: data.SendBy, + SendByName: data.SendByName, + Message: data.Message, + IsRead: data.IsRead, + IsActive: data.IsActive, + CreatedAt: data.CreatedAt, + UpdatedAt: data.UpdatedAt, + }, nil +} + +func (s *notificationsService) ListBySentTo(sentTo int, page int, limit int, isRead *bool) ([]response.NotificationResponse, int64, error) { + if sentTo <= 0 { + return nil, 0, errors.New("sentTo is required") + } + + rows, total, err := s.Repo.ListBySentTo(sentTo, page, limit, isRead) + if err != nil { + return nil, 0, err + } + + result := make([]response.NotificationResponse, 0) + for _, item := range rows { + result = append(result, response.NotificationResponse{ + ID: item.ID, + SentTo: item.SentTo, + SendBy: item.SendBy, + SendByName: item.SendByName, + Message: item.Message, + IsRead: item.IsRead, + IsActive: item.IsActive, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + }) + } + + return result, total, nil +} + +func (s *notificationsService) MarkRead(id uint64) error { + old, err := s.Repo.FindById(id) + if err != nil { + return err + } + + if old.IsActive == false { + return errors.New("notification already deleted") + } + + return s.Repo.MarkRead(id) +} + +func (s *notificationsService) Delete(id uint64) error { + return s.Repo.Delete(id) +} diff --git a/app/router/api.go b/app/router/api.go index 319fc3f..0a8726d 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -23,6 +23,7 @@ import ( "narasi-ahli-be/app/module/magazines" "narasi-ahli-be/app/module/master_menus" "narasi-ahli-be/app/module/master_modules" + "narasi-ahli-be/app/module/notifications" "narasi-ahli-be/app/module/provinces" "narasi-ahli-be/app/module/research_journals" "narasi-ahli-be/app/module/subscription" @@ -72,6 +73,7 @@ type Router struct { WorkHistoryRouter *work_history.WorkHistoryRouter ResearchJournalsRouter *research_journals.ResearchJournalsRouter AIChatFilesRouter *ai_chat_files.AiChatFilesRouter + NotificationRouter *notifications.NotificationRouter } @@ -109,6 +111,8 @@ func NewRouter( workHistoryRouter *work_history.WorkHistoryRouter, researchJournalsRouter *research_journals.ResearchJournalsRouter, aiChatFilesRouter *ai_chat_files.AiChatFilesRouter, + notificationRouter *notifications.NotificationRouter, + ) *Router { return &Router{ App: fiber, @@ -143,6 +147,8 @@ func NewRouter( WorkHistoryRouter: workHistoryRouter, ResearchJournalsRouter: researchJournalsRouter, AIChatFilesRouter: aiChatFilesRouter, + NotificationRouter: notificationRouter, + } } @@ -187,5 +193,7 @@ func (r *Router) Register() { r.WorkHistoryRouter.RegisterWorkHistoryRoutes() r.ResearchJournalsRouter.RegisterResearchJournalsRoutes() r.AIChatFilesRouter.RegisterAiChatFilesRoutes() + r.NotificationRouter.RegisterNotificationRoutes() + } diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 32b81ab..bcf957b 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -12354,6 +12354,254 @@ const docTemplate = `{ } } }, + "/notifications": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk membuat notifikasi dan mengirim ke user berdasarkan sentTo", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifications" + ], + "summary": "Create Notification", + "parameters": [ + { + "description": "Create Notification Payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateNotificationRequest" + } + } + ], + "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" + } + } + } + } + }, + "/notifications/{id}": { + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk soft delete notifikasi (isActive=false)", + "tags": [ + "Notifications" + ], + "summary": "Soft Delete Notification", + "parameters": [ + { + "type": "integer", + "description": "Notification 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" + } + } + } + } + }, + "/notifications/{id}/read": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk mengubah status notifikasi menjadi sudah dibaca", + "tags": [ + "Notifications" + ], + "summary": "Mark Read Notification", + "parameters": [ + { + "type": "integer", + "description": "Notification 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" + } + } + } + } + }, + "/notifications/{sentTo}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk mengambil list notifikasi berdasarkan penerima (sentTo)", + "tags": [ + "Notifications" + ], + "summary": "List Notification By SentTo", + "parameters": [ + { + "type": "integer", + "description": "SentTo (Receiver User ID)", + "name": "sentTo", + "in": "path", + "required": true + }, + { + "type": "integer", + "default": 1, + "description": "Page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter isRead (true/false)", + "name": "isRead", + "in": "query" + } + ], + "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": [ @@ -16725,6 +16973,29 @@ const docTemplate = `{ } } }, + "request.CreateNotificationRequest": { + "type": "object", + "required": [ + "message", + "sendBy", + "sendByName", + "sentTo" + ], + "properties": { + "message": { + "type": "string" + }, + "sendBy": { + "type": "integer" + }, + "sendByName": { + "type": "string" + }, + "sentTo": { + "type": "integer" + } + } + }, "request.CustomStaticPagesCreateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index aae9040..60d7fe4 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -12343,6 +12343,254 @@ } } }, + "/notifications": { + "post": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk membuat notifikasi dan mengirim ke user berdasarkan sentTo", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Notifications" + ], + "summary": "Create Notification", + "parameters": [ + { + "description": "Create Notification Payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.CreateNotificationRequest" + } + } + ], + "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" + } + } + } + } + }, + "/notifications/{id}": { + "delete": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk soft delete notifikasi (isActive=false)", + "tags": [ + "Notifications" + ], + "summary": "Soft Delete Notification", + "parameters": [ + { + "type": "integer", + "description": "Notification 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" + } + } + } + } + }, + "/notifications/{id}/read": { + "put": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk mengubah status notifikasi menjadi sudah dibaca", + "tags": [ + "Notifications" + ], + "summary": "Mark Read Notification", + "parameters": [ + { + "type": "integer", + "description": "Notification 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" + } + } + } + } + }, + "/notifications/{sentTo}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API untuk mengambil list notifikasi berdasarkan penerima (sentTo)", + "tags": [ + "Notifications" + ], + "summary": "List Notification By SentTo", + "parameters": [ + { + "type": "integer", + "description": "SentTo (Receiver User ID)", + "name": "sentTo", + "in": "path", + "required": true + }, + { + "type": "integer", + "default": 1, + "description": "Page", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Limit", + "name": "limit", + "in": "query" + }, + { + "type": "boolean", + "description": "Filter isRead (true/false)", + "name": "isRead", + "in": "query" + } + ], + "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": [ @@ -16714,6 +16962,29 @@ } } }, + "request.CreateNotificationRequest": { + "type": "object", + "required": [ + "message", + "sendBy", + "sendByName", + "sentTo" + ], + "properties": { + "message": { + "type": "string" + }, + "sendBy": { + "type": "integer" + }, + "sendByName": { + "type": "string" + }, + "sentTo": { + "type": "integer" + } + } + }, "request.CustomStaticPagesCreateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 1b5e029..769a759 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -588,6 +588,22 @@ definitions: - id - provId type: object + request.CreateNotificationRequest: + properties: + message: + type: string + sendBy: + type: integer + sendByName: + type: string + sentTo: + type: integer + required: + - message + - sendBy + - sendByName + - sentTo + type: object request.CustomStaticPagesCreateRequest: properties: description: @@ -9308,6 +9324,165 @@ paths: summary: Update MasterStatuses tags: - Untags + /notifications: + post: + consumes: + - application/json + description: API untuk membuat notifikasi dan mengirim ke user berdasarkan sentTo + parameters: + - description: Create Notification Payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/request.CreateNotificationRequest' + 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: Create Notification + tags: + - Notifications + /notifications/{id}: + delete: + description: API untuk soft delete notifikasi (isActive=false) + parameters: + - description: Notification 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: Soft Delete Notification + tags: + - Notifications + /notifications/{id}/read: + put: + description: API untuk mengubah status notifikasi menjadi sudah dibaca + parameters: + - description: Notification 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: Mark Read Notification + tags: + - Notifications + /notifications/{sentTo}: + get: + description: API untuk mengambil list notifikasi berdasarkan penerima (sentTo) + parameters: + - description: SentTo (Receiver User ID) + in: path + name: sentTo + required: true + type: integer + - default: 1 + description: Page + in: query + name: page + type: integer + - default: 10 + description: Limit + in: query + name: limit + type: integer + - description: Filter isRead (true/false) + in: query + name: isRead + type: boolean + 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: List Notification By SentTo + tags: + - Notifications /provinces: get: description: API for getting all Provinces diff --git a/main.go b/main.go index 57f8c4b..d2027ad 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ import ( "narasi-ahli-be/app/module/magazines" "narasi-ahli-be/app/module/master_menus" "narasi-ahli-be/app/module/master_modules" + "narasi-ahli-be/app/module/notifications" "narasi-ahli-be/app/module/provinces" "narasi-ahli-be/app/module/research_journals" "narasi-ahli-be/app/module/subscription" @@ -99,6 +100,8 @@ func main() { work_history.NewWorkHistoryModule, research_journals.NewResearchJournalsModule, ai_chat_files.NewAiChatFilesModule, + notifications.NewNotificationsModule, + // start aplication fx.Invoke(webserver.Start),