diff --git a/app/database/entity/agent.entity.go b/app/database/entity/agent.entity.go new file mode 100644 index 0000000..f85332c --- /dev/null +++ b/app/database/entity/agent.entity.go @@ -0,0 +1,16 @@ +package entity + +import "time" + +type Agent struct { + ID uint `gorm:"primaryKey" json:"id"` + AgentID string `gorm:"type:varchar(100);uniqueIndex" json:"agent_id"` + Name string `gorm:"type:varchar(255)" json:"name"` + Description string `gorm:"type:text" json:"description"` + Instructions string `gorm:"type:text" json:"instructions"` + Type string `gorm:"type:varchar(100)" json:"type"` + Status bool `gorm:"default:true" json:"status"` + IsActive bool `gorm:"default:true" json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} \ No newline at end of file diff --git a/app/module/agent/agent.module.go b/app/module/agent/agent.module.go new file mode 100644 index 0000000..27b731f --- /dev/null +++ b/app/module/agent/agent.module.go @@ -0,0 +1,47 @@ +package agent + +import ( + "narasi-ahli-be/app/module/agent/controller" + "narasi-ahli-be/app/module/agent/repository" + "narasi-ahli-be/app/module/agent/service" + + "github.com/gofiber/fiber/v2" + "go.uber.org/fx" +) + +type AgentRouter struct { + App fiber.Router + Controller *controller.Controller +} + +var NewAgentModule = fx.Options( + // register repository of Agent module + fx.Provide(repository.NewAgentRepository), + + // register service of Agent module + fx.Provide(service.NewAgentService), + + // register controller of Agent module + fx.Provide(controller.NewController), + + // register router of Agent module + fx.Provide(NewAgentRouter), +) + +func NewAgentRouter(fiber *fiber.App, controller *controller.Controller) *AgentRouter { + return &AgentRouter{ + App: fiber, + Controller: controller, + } +} + +func (_i *AgentRouter) RegisterAgentRoutes() { + agentController := _i.Controller.Agent + + _i.App.Route("/agent", func(router fiber.Router) { + router.Get("/", agentController.All) + router.Get("/:id", agentController.Show) + router.Post("/", agentController.Save) + router.Delete("/:id", agentController.Delete) + }) +} \ No newline at end of file diff --git a/app/module/agent/controller/agent.controller.go b/app/module/agent/controller/agent.controller.go new file mode 100644 index 0000000..7d77b5e --- /dev/null +++ b/app/module/agent/controller/agent.controller.go @@ -0,0 +1,138 @@ +package controller + +import ( + "narasi-ahli-be/app/module/agent/request" + "narasi-ahli-be/app/module/agent/service" + utilRes "narasi-ahli-be/utils/response" + utilVal "narasi-ahli-be/utils/validator" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type agentController struct { + agentService service.AgentService +} + +type AgentController 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 NewAgentController(agentService service.AgentService) AgentController { + return &agentController{agentService: agentService} +} + +// All godoc +// @Summary Get list agent +// @Description Get list agent +// @Tags Agent +// @Accept json +// @Produce json +// @Param name query string false "Agent name" +// @Param type query string false "Agent type" +// @Success 200 {object} utilRes.Response +// @Router /agent [get] +func (_i *agentController) All(c *fiber.Ctx) error { + req := request.AgentQueryRequest{ + Name: c.Query("name"), + Type: c.Query("type"), + } + + data, err := _i.agentService.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Data: data, + }) +} + + +// Show godoc +// @Summary Get agent by ID +// @Tags Agent +// @Accept json +// @Produce json +// @Param id path int true "Agent ID" +// @Success 200 {object} utilRes.Response +// @Router /agent/{id} [get] +func (_i *agentController) Show(c *fiber.Ctx) error { + id, _ := strconv.ParseUint(c.Params("id"), 10, 64) + data, err := _i.agentService.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{Success: true, Data: data}) +} + + +// Save godoc +// @Summary Create agent +// @Tags Agent +// @Accept json +// @Produce json +// @Param body body request.AgentCreateRequest true "Agent payload" +// @Success 200 {object} utilRes.Response +// @Router /agent [post] +func (_i *agentController) Save(c *fiber.Ctx) error { + req := new(request.AgentCreateRequest) + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + data, err := _i.agentService.Save(*req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{Success: true, Data: data}) +} + + +// Update godoc +// @Summary Update agent +// @Tags Agent +// @Accept json +// @Produce json +// @Param id path int true "Agent ID" +// @Param body body request.AgentUpdateRequest true "Agent payload" +// @Success 200 {object} utilRes.Response +// @Router /agent/{id} [put] +func (_i *agentController) Update(c *fiber.Ctx) error { + id, _ := strconv.ParseUint(c.Params("id"), 10, 64) + req := new(request.AgentUpdateRequest) + + if err := utilVal.ParseAndValidate(c, req); err != nil { + return err + } + + if err := _i.agentService.Update(uint(id), *req); err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{Success: true}) +} + + +// Delete godoc +// @Summary Delete agent +// @Tags Agent +// @Accept json +// @Produce json +// @Param id path int true "Agent ID" +// @Success 200 {object} utilRes.Response +// @Router /agent/{id} [delete] +func (_i *agentController) Delete(c *fiber.Ctx) error { + id, _ := strconv.ParseUint(c.Params("id"), 10, 64) + if err := _i.agentService.Delete(uint(id)); err != nil { + return err + } + return utilRes.Resp(c, utilRes.Response{Success: true}) +} diff --git a/app/module/agent/controller/controller.go b/app/module/agent/controller/controller.go new file mode 100644 index 0000000..185a10f --- /dev/null +++ b/app/module/agent/controller/controller.go @@ -0,0 +1,13 @@ +package controller + +import "narasi-ahli-be/app/module/agent/service" + +type Controller struct { + Agent AgentController +} + +func NewController(agentService service.AgentService) *Controller { + return &Controller{ + Agent: NewAgentController(agentService), + } +} diff --git a/app/module/agent/mapper/agent.mapper.go b/app/module/agent/mapper/agent.mapper.go new file mode 100644 index 0000000..a05c0d1 --- /dev/null +++ b/app/module/agent/mapper/agent.mapper.go @@ -0,0 +1,25 @@ +package mapper + +import ( + "narasi-ahli-be/app/database/entity" + "narasi-ahli-be/app/module/agent/response" +) + +func AgentResponseMapper(agent *entity.Agent) *response.AgentResponse { + if agent == nil { + return nil + } + + return &response.AgentResponse{ + ID: agent.ID, + AgentID: agent.AgentID, + Name: agent.Name, + Description: agent.Description, + Instructions: agent.Instructions, + Type: agent.Type, + Status: agent.Status, + IsActive: agent.IsActive, + CreatedAt: agent.CreatedAt, + UpdatedAt: agent.UpdatedAt, + } +} \ No newline at end of file diff --git a/app/module/agent/repository/agent.repository.go b/app/module/agent/repository/agent.repository.go new file mode 100644 index 0000000..750fe0e --- /dev/null +++ b/app/module/agent/repository/agent.repository.go @@ -0,0 +1,70 @@ +package repository + +import ( + "narasi-ahli-be/app/database" + "narasi-ahli-be/app/database/entity" + "narasi-ahli-be/app/module/agent/request" +) + +type agentRepository struct { + DB *database.Database +} + +type AgentRepository interface { + GetAll(req request.AgentQueryRequest) ([]*entity.Agent, error) + FindById(id uint) (*entity.Agent, error) + Create(agent *entity.Agent) (*entity.Agent, error) + Update(id uint, data map[string]interface{}) error + Delete(id uint) error +} + +func NewAgentRepository(db *database.Database) AgentRepository { + return &agentRepository{DB: db} +} + +func (_i *agentRepository) GetAll(req request.AgentQueryRequest) (agents []*entity.Agent, err error) { + query := _i.DB.DB.Model(&entity.Agent{}). + Where("is_active = ?", true) + + if req.Name != "" { + query = query.Where("name ILIKE ?", "%"+req.Name+"%") + } + if req.Type != "" { + query = query.Where("type = ?", req.Type) + } + if req.Status != nil { + query = query.Where("status = ?", *req.Status) + } + + err = query.Order("created_at DESC").Find(&agents).Error + return +} + +func (_i *agentRepository) FindById(id uint) (*entity.Agent, error) { + var agent entity.Agent + err := _i.DB.DB.Where("id = ? AND is_active = ?", id, true). + First(&agent).Error + if err != nil { + return nil, err + } + return &agent, nil +} + +func (_i *agentRepository) Create(agent *entity.Agent) (*entity.Agent, error) { + if err := _i.DB.DB.Create(agent).Error; err != nil { + return nil, err + } + return agent, nil +} + +func (_i *agentRepository) Update(id uint, data map[string]interface{}) error { + return _i.DB.DB.Model(&entity.Agent{}). + Where("id = ?", id). + Updates(data).Error +} + +func (_i *agentRepository) Delete(id uint) error { + return _i.DB.DB.Model(&entity.Agent{}). + Where("id = ?", id). + Update("is_active", false).Error +} diff --git a/app/module/agent/request/agent.request.go b/app/module/agent/request/agent.request.go new file mode 100644 index 0000000..9c048b6 --- /dev/null +++ b/app/module/agent/request/agent.request.go @@ -0,0 +1,75 @@ +package request + +import "narasi-ahli-be/app/database/entity" + +type AgentCreateRequest struct { + AgentID string `json:"agent_id" validate:"required"` + Name string `json:"name" validate:"required"` + Description string `json:"description"` + Instructions string `json:"instructions"` + Type string `json:"type" validate:"required"` + Status *bool `json:"status"` + IsActive *bool `json:"is_active"` +} + +func (r *AgentCreateRequest) ToEntity() *entity.Agent { + status := true + if r.Status != nil { + status = *r.Status + } + + isActive := true + if r.IsActive != nil { + isActive = *r.IsActive + } + + return &entity.Agent{ + AgentID: r.AgentID, + Name: r.Name, + Description: r.Description, + Instructions: r.Instructions, + Type: r.Type, + Status: status, + IsActive: isActive, + } +} + +type AgentUpdateRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + Instructions *string `json:"instructions"` + Type *string `json:"type"` + Status *bool `json:"status"` + IsActive *bool `json:"is_active"` +} + +func (r *AgentUpdateRequest) ToMap() map[string]interface{} { + data := map[string]interface{}{} + + if r.Name != nil { + data["name"] = *r.Name + } + if r.Description != nil { + data["description"] = *r.Description + } + if r.Instructions != nil { + data["instructions"] = *r.Instructions + } + if r.Type != nil { + data["type"] = *r.Type + } + if r.Status != nil { + data["status"] = *r.Status + } + if r.IsActive != nil { + data["is_active"] = *r.IsActive + } + + return data +} + +type AgentQueryRequest struct { + Name string `query:"name"` + Type string `query:"type"` + Status *bool `query:"status"` +} diff --git a/app/module/agent/response/agent.response.go b/app/module/agent/response/agent.response.go new file mode 100644 index 0000000..88c41f4 --- /dev/null +++ b/app/module/agent/response/agent.response.go @@ -0,0 +1,16 @@ +package response + +import "time" + +type AgentResponse struct { + ID uint `json:"id"` + AgentID string `json:"agentId"` + Name string `json:"name"` + Description string `json:"description"` + Instructions string `json:"instructions"` + Type string `json:"type"` + Status bool `json:"status"` + 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/agent/service/agent.service.go b/app/module/agent/service/agent.service.go new file mode 100644 index 0000000..4413885 --- /dev/null +++ b/app/module/agent/service/agent.service.go @@ -0,0 +1,73 @@ +package service + +import ( + "errors" + "narasi-ahli-be/app/module/agent/mapper" + "narasi-ahli-be/app/module/agent/repository" + "narasi-ahli-be/app/module/agent/request" + "narasi-ahli-be/app/module/agent/response" + + "github.com/rs/zerolog" +) + +type agentService struct { + Repo repository.AgentRepository + Log zerolog.Logger +} + +type AgentService interface { + All(req request.AgentQueryRequest) ([]*response.AgentResponse, error) + Show(id uint) (*response.AgentResponse, error) + Save(req request.AgentCreateRequest) (*response.AgentResponse, error) + Update(id uint, req request.AgentUpdateRequest) error + Delete(id uint) error +} + +func NewAgentService(repo repository.AgentRepository, log zerolog.Logger) AgentService { + return &agentService{Repo: repo, Log: log} +} + +func (_i *agentService) All(req request.AgentQueryRequest) (agents []*response.AgentResponse, err error) { + results, err := _i.Repo.GetAll(req) + if err != nil { + return + } + + for _, result := range results { + agents = append(agents, mapper.AgentResponseMapper(result)) + } + return +} + +func (_i *agentService) Show(id uint) (*response.AgentResponse, error) { + result, err := _i.Repo.FindById(id) + if err != nil { + return nil, errors.New("agent not found") + } + return mapper.AgentResponseMapper(result), nil +} + +func (_i *agentService) Save(req request.AgentCreateRequest) (*response.AgentResponse, error) { + entity := req.ToEntity() + result, err := _i.Repo.Create(entity) + if err != nil { + return nil, err + } + return mapper.AgentResponseMapper(result), nil +} + +func (_i *agentService) Update(id uint, req request.AgentUpdateRequest) error { + _, err := _i.Repo.FindById(id) + if err != nil { + return errors.New("agent not found") + } + return _i.Repo.Update(id, req.ToMap()) +} + +func (_i *agentService) Delete(id uint) error { + _, err := _i.Repo.FindById(id) + if err != nil { + return errors.New("agent not found") + } + return _i.Repo.Delete(id) +} diff --git a/app/module/users/controller/controller.go b/app/module/users/controller/controller.go index 0a544a1..a9628da 100644 --- a/app/module/users/controller/controller.go +++ b/app/module/users/controller/controller.go @@ -4,10 +4,15 @@ import "narasi-ahli-be/app/module/users/service" type Controller struct { Users UsersController + UserExpert UserExpertController + } -func NewController(UsersService service.UsersService) *Controller { +func NewController(UsersService service.UsersService, userExpert UserExpertController, +) *Controller { return &Controller{ Users: NewUsersController(UsersService), + UserExpert: userExpert, + } } diff --git a/app/module/users/controller/user-expert.controller.go b/app/module/users/controller/user-expert.controller.go new file mode 100644 index 0000000..2f07f0a --- /dev/null +++ b/app/module/users/controller/user-expert.controller.go @@ -0,0 +1,92 @@ +package controller + +import ( + "narasi-ahli-be/app/module/users/request" + "narasi-ahli-be/app/module/users/service" + utilRes "narasi-ahli-be/utils/response" + "strconv" + + "github.com/gofiber/fiber/v2" +) + +type userExpertController struct { + service service.UserExpertService +} + +type UserExpertController interface { + All(c *fiber.Ctx) error + Show(c *fiber.Ctx) error +} + +func NewUserExpertController( + service service.UserExpertService, +) UserExpertController { + return &userExpertController{ + service: service, + } +} + +// @Summary Get list expert users +// @Description Get users with role expert (user_role_id = 2) +// @Tags User Expert +// @Param name query string false "Filter by expert name" +// @Param type query string false "Filter by expert type" +// @Success 200 {object} response.Response +// @Failure 500 {object} response.InternalServerError +// @Router /users/experts [get] +func (_i *userExpertController) All(c *fiber.Ctx) error { + var namePtr *string + var typePtr *string + + name := c.Query("name") + if name != "" { + namePtr = &name + } + + t := c.Query("type") + if t != "" { + typePtr = &t + } + + req := request.UserExpertQueryRequest{ + Name: namePtr, + Type: typePtr, + } + + data, err := _i.service.All(req) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Data: data, + }) +} +// Show Expert User +// @Summary Get expert user detail +// @Description Get expert user with work, education, and research histories +// @Tags User Expert +// @Param id path int true "User ID" +// @Success 200 {object} response.Response +// @Failure 404 {object} response.BadRequestError +// @Failure 500 {object} response.InternalServerError +// @Router /users/experts/{id} [get] +func (_i *userExpertController) Show(c *fiber.Ctx) error { + + id, err := strconv.ParseUint(c.Params("id"), 10, 64) + if err != nil { + return err + } + + data, err := _i.service.Show(uint(id)) + if err != nil { + return err + } + + return utilRes.Resp(c, utilRes.Response{ + Success: true, + Data: data, + }) +} + diff --git a/app/module/users/repository/user-expert.repository.go b/app/module/users/repository/user-expert.repository.go new file mode 100644 index 0000000..ef598fa --- /dev/null +++ b/app/module/users/repository/user-expert.repository.go @@ -0,0 +1,73 @@ +package repository + +import ( + "fmt" + "narasi-ahli-be/app/database" + "narasi-ahli-be/app/database/entity/users" + "narasi-ahli-be/app/module/users/request" +) + +type UserExpertRepository interface { + GetExperts(req request.UserExpertQueryRequest) ([]*users.Users, error) + FindExpertByID(id uint) (*users.Users, error) +} + +type userExpertRepository struct { + DB *database.Database +} + +func NewUserExpertRepository(db *database.Database) UserExpertRepository { + return &userExpertRepository{DB: db} +} + +func (r *userExpertRepository) GetExperts( + req request.UserExpertQueryRequest, +) ([]*users.Users, error) { + + fmt.Println("=== GetExperts CALLED ===") + fmt.Printf("repo instance: %+v\n", r) + + if r.DB == nil { + panic("🔥 r.DB IS NIL") + } + + if r.DB.DB == nil { + panic("🔥 r.DB.DB IS NIL") + } + + var res []*users.Users + + query := r.DB.DB. + Debug(). // 🔥 TAMBAHKAN INI + Model(&users.Users{}). + Where( + "user_role_id = ? AND (is_active = true OR is_active IS NULL)", + 3, + ) + if req.Name != nil { + query = query.Where("fullname ILIKE ?", "%"+*req.Name+"%") + } + + if req.Type != nil { + query = query.Where("work_type = ?", *req.Type) + } + + err := query.Find(&res).Error + return res, err +} + +func (r *userExpertRepository) FindExpertByID(id uint) (*users.Users, error) { + var user users.Users + err := r.DB.DB. + Where( + "id = ? AND user_role_id = ? AND (is_active = true OR is_active IS NULL)", + id, 3, + ). + First(&user). + Error + + if err != nil { + return nil, err + } + return &user, nil +} diff --git a/app/module/users/request/user-expert.request.go b/app/module/users/request/user-expert.request.go new file mode 100644 index 0000000..c687593 --- /dev/null +++ b/app/module/users/request/user-expert.request.go @@ -0,0 +1,6 @@ +package request + +type UserExpertQueryRequest struct { + Name *string `query:"name"` + Type *string `query:"type"` +} diff --git a/app/module/users/response/users.response.go b/app/module/users/response/users.response.go index 12f39e3..15edad2 100644 --- a/app/module/users/response/users.response.go +++ b/app/module/users/response/users.response.go @@ -1,6 +1,11 @@ package response -import "time" +import ( + eduRes "narasi-ahli-be/app/module/education_history/response" + researchRes "narasi-ahli-be/app/module/research_journals/response" + workRes "narasi-ahli-be/app/module/work_history/response" + "time" +) type UsersResponse struct { ID uint `json:"id"` @@ -36,3 +41,18 @@ type ParetoLoginResponse struct { type VisitorStatistic struct { TotalVisitor string `json:"accessToken"` } + +type UserExpertResponse struct { + ID uint `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Fullname string `json:"fullname"` + Phone *string `json:"phoneNumber"` + LastJob *string `json:"lastJobTitle"` + IsActive *bool `json:"isActive"` + CreatedAt time.Time `json:"createdAt"` + + WorkHistories []*workRes.WorkHistoryResponse `json:"workHistories"` + EducationHistories []*eduRes.EducationHistoryResponse `json:"educationHistories"` + ResearchJournals []*researchRes.ResearchJournalsResponse `json:"researchJournals"` +} diff --git a/app/module/users/service/user-expert.service.go b/app/module/users/service/user-expert.service.go new file mode 100644 index 0000000..669be53 --- /dev/null +++ b/app/module/users/service/user-expert.service.go @@ -0,0 +1,171 @@ +package service + +import ( + "errors" + "narasi-ahli-be/app/database/entity/users" + eduMapper "narasi-ahli-be/app/module/education_history/mapper" + eduRepo "narasi-ahli-be/app/module/education_history/repository" + eduReq "narasi-ahli-be/app/module/education_history/request" + researchMapper "narasi-ahli-be/app/module/research_journals/mapper" + researchRepo "narasi-ahli-be/app/module/research_journals/repository" + researchReq "narasi-ahli-be/app/module/research_journals/request" + userRepo "narasi-ahli-be/app/module/users/repository" + "narasi-ahli-be/app/module/users/request" + "narasi-ahli-be/app/module/users/response" + workMapper "narasi-ahli-be/app/module/work_history/mapper" + workRepo "narasi-ahli-be/app/module/work_history/repository" + workReq "narasi-ahli-be/app/module/work_history/request" + "narasi-ahli-be/utils/paginator" + + "github.com/rs/zerolog" +) + +type UserExpertService interface { + All(req request.UserExpertQueryRequest) ([]*response.UserExpertResponse, error) + Show(id uint) (*response.UserExpertResponse, error) +} + + +type userExpertService struct { + UserRepo userRepo.UserExpertRepository + WorkRepo workRepo.WorkHistoryRepository + EduRepo eduRepo.EducationHistoryRepository + ResearchRepo researchRepo.ResearchJournalsRepository + Log zerolog.Logger +} + +func NewUserExpertService( + userRepo userRepo.UserExpertRepository, + workRepo workRepo.WorkHistoryRepository, + eduRepo eduRepo.EducationHistoryRepository, + researchRepo researchRepo.ResearchJournalsRepository, + log zerolog.Logger, +) UserExpertService { + return &userExpertService{ + UserRepo: userRepo, + WorkRepo: workRepo, + EduRepo: eduRepo, + ResearchRepo: researchRepo, + Log: log, + } +} + +func (s *userExpertService) All( + req request.UserExpertQueryRequest, +) ([]*response.UserExpertResponse, error) { + + var users []*users.Users + var err error + + +if s.UserRepo == nil { + s.Log.Error().Msg("UserRepo IS NIL") + return nil, errors.New("internal server error") +} + + + // 🔥 pakai filter jika ada + users, err = s.UserRepo.GetExperts(req) + + if err != nil { + return nil, err + } + + + + + var results []*response.UserExpertResponse + +workReq := workReq.WorkHistoryQueryRequest{ + Pagination: &paginator.Pagination{}, +} + +eduReq := eduReq.EducationHistoryQueryRequest{ + Pagination: &paginator.Pagination{}, +} + +researchReq := researchReq.ResearchJournalsQueryRequest{ + Pagination: &paginator.Pagination{}, +} + + for _, user := range users { + + works, _, _ := s.WorkRepo.GetAll(user.ID, workReq) + educations, _, _ := s.EduRepo.GetAll(user.ID, eduReq) + researches, _, _ := s.ResearchRepo.GetAll(user.ID, researchReq) + + res := &response.UserExpertResponse{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + Fullname: user.Fullname, + Phone: user.PhoneNumber, + LastJob: user.LastJobTitle, + IsActive: user.IsActive, + CreatedAt: user.CreatedAt, + } + + for _, w := range works { + res.WorkHistories = append( + res.WorkHistories, + workMapper.WorkHistoryResponseMapper(w), + ) + } + + for _, e := range educations { + res.EducationHistories = append( + res.EducationHistories, + eduMapper.EducationHistoryResponseMapper(e), + ) + } + + for _, r := range researches { + res.ResearchJournals = append( + res.ResearchJournals, + researchMapper.ResearchJournalsResponseMapper(r), + ) + } + + results = append(results, res) + } + + return results, nil +} + +func (s *userExpertService) Show(id uint) (*response.UserExpertResponse, error) { + user, err := s.UserRepo.FindExpertByID(id) + if err != nil { + return nil, errors.New("expert not found") + } + + req := workReq.WorkHistoryQueryRequest{} + researchReq := researchReq.ResearchJournalsQueryRequest{} + eduReq := eduReq.EducationHistoryQueryRequest{} + + works, _, _ := s.WorkRepo.GetAll(id,req) + educations, _, _ := s.EduRepo.GetAll(id, eduReq) + researches, _, _ := s.ResearchRepo.GetAll(id, researchReq) + + res := &response.UserExpertResponse{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + Fullname: user.Fullname, + Phone: user.PhoneNumber, + LastJob: user.LastJobTitle, + IsActive: user.IsActive, + CreatedAt: user.CreatedAt, + } + + for _, w := range works { + res.WorkHistories = append(res.WorkHistories, workMapper.WorkHistoryResponseMapper(w)) + } + for _, e := range educations { + res.EducationHistories = append(res.EducationHistories, eduMapper.EducationHistoryResponseMapper(e)) + } + for _, r := range researches { + res.ResearchJournals = append(res.ResearchJournals, researchMapper.ResearchJournalsResponseMapper(r)) + } + + return res, nil +} diff --git a/app/module/users/users.module.go b/app/module/users/users.module.go index d395d09..d2296c1 100644 --- a/app/module/users/users.module.go +++ b/app/module/users/users.module.go @@ -28,6 +28,10 @@ var NewUsersModule = fx.Options( // register router of Users module fx.Provide(NewUsersRouter), + + fx.Provide(repository.NewUserExpertRepository), + fx.Provide(service.NewUserExpertService), + fx.Provide(controller.NewUserExpertController), ) // init UsersRouter @@ -41,6 +45,8 @@ func NewUsersRouter(fiber *fiber.App, controller *controller.Controller) *UsersR // register routes of Users module func (_i *UsersRouter) RegisterUsersRoutes() { // define controllers + _i.RegisterUserExpertRoutes() + usersController := _i.Controller.Users // define routes @@ -62,4 +68,15 @@ func (_i *UsersRouter) RegisterUsersRoutes() { router.Post("/email-validation", usersController.EmailValidation) router.Post("/setup-email", usersController.SetupEmail) }) + +} + +func (_i *UsersRouter) RegisterUserExpertRoutes() { + + expertController := _i.Controller.UserExpert + + _i.App.Route("/users/experts", func(router fiber.Router) { + router.Get("/", expertController.All) + router.Get("/:id", expertController.Show) + }) } diff --git a/app/router/api.go b/app/router/api.go index 0a8726d..a55274e 100644 --- a/app/router/api.go +++ b/app/router/api.go @@ -3,6 +3,7 @@ package router import ( "narasi-ahli-be/app/module/activity_logs" "narasi-ahli-be/app/module/advertisement" + "narasi-ahli-be/app/module/agent" "narasi-ahli-be/app/module/ai_chat" "narasi-ahli-be/app/module/ai_chat_files" "narasi-ahli-be/app/module/article_approvals" @@ -74,7 +75,7 @@ type Router struct { ResearchJournalsRouter *research_journals.ResearchJournalsRouter AIChatFilesRouter *ai_chat_files.AiChatFilesRouter NotificationRouter *notifications.NotificationRouter - + AgentRouter *agent.AgentRouter } func NewRouter( @@ -112,6 +113,7 @@ func NewRouter( researchJournalsRouter *research_journals.ResearchJournalsRouter, aiChatFilesRouter *ai_chat_files.AiChatFilesRouter, notificationRouter *notifications.NotificationRouter, + agentRouter *agent.AgentRouter, ) *Router { return &Router{ @@ -148,6 +150,7 @@ func NewRouter( ResearchJournalsRouter: researchJournalsRouter, AIChatFilesRouter: aiChatFilesRouter, NotificationRouter: notificationRouter, + AgentRouter: agentRouter, } } @@ -194,6 +197,7 @@ func (r *Router) Register() { r.ResearchJournalsRouter.RegisterResearchJournalsRoutes() r.AIChatFilesRouter.RegisterAiChatFilesRoutes() r.NotificationRouter.RegisterNotificationRoutes() + r.AgentRouter.RegisterAgentRoutes() } diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 24cb6fa..d0b62dc 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -914,6 +914,172 @@ const docTemplate = `{ } } }, + "/agent": { + "get": { + "description": "Get list agent", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Get list agent", + "parameters": [ + { + "type": "string", + "description": "Agent name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Agent type", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Create agent", + "parameters": [ + { + "description": "Agent payload", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AgentCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/agent/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Get agent by ID", + "parameters": [ + { + "type": "integer", + "description": "Agent ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Update agent", + "parameters": [ + { + "type": "integer", + "description": "Agent ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Agent payload", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AgentUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Delete agent", + "parameters": [ + { + "type": "integer", + "description": "Agent ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/ai-chat-files": { "get": { "security": [ @@ -15138,6 +15304,81 @@ const docTemplate = `{ } } }, + "/users/experts": { + "get": { + "description": "Get users with role expert (user_role_id = 2)", + "tags": [ + "User Expert" + ], + "summary": "Get list expert users", + "parameters": [ + { + "type": "string", + "description": "Filter by expert name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by expert type", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/experts/{id}": { + "get": { + "description": "Get expert user with work, education, and research histories", + "tags": [ + "User Expert" + ], + "summary": "Get expert user detail", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/users/forgot-password": { "post": { "security": [ @@ -16352,6 +16593,60 @@ const docTemplate = `{ } } }, + "request.AgentCreateRequest": { + "type": "object", + "required": [ + "agent_id", + "name", + "type" + ], + "properties": { + "agent_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "status": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "request.AgentUpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "status": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, "request.AiChatFilesUpdateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 30c230e..57aafae 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -903,6 +903,172 @@ } } }, + "/agent": { + "get": { + "description": "Get list agent", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Get list agent", + "parameters": [ + { + "type": "string", + "description": "Agent name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Agent type", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Create agent", + "parameters": [ + { + "description": "Agent payload", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AgentCreateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, + "/agent/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Get agent by ID", + "parameters": [ + { + "type": "integer", + "description": "Agent ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "put": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Update agent", + "parameters": [ + { + "type": "integer", + "description": "Agent ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Agent payload", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.AgentUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + }, + "delete": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agent" + ], + "summary": "Delete agent", + "parameters": [ + { + "type": "integer", + "description": "Agent ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + } + } + } + }, "/ai-chat-files": { "get": { "security": [ @@ -15127,6 +15293,81 @@ } } }, + "/users/experts": { + "get": { + "description": "Get users with role expert (user_role_id = 2)", + "tags": [ + "User Expert" + ], + "summary": "Get list expert users", + "parameters": [ + { + "type": "string", + "description": "Filter by expert name", + "name": "name", + "in": "query" + }, + { + "type": "string", + "description": "Filter by expert type", + "name": "type", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, + "/users/experts/{id}": { + "get": { + "description": "Get expert user with work, education, and research histories", + "tags": [ + "User Expert" + ], + "summary": "Get expert user detail", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Response" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/users/forgot-password": { "post": { "security": [ @@ -16341,6 +16582,60 @@ } } }, + "request.AgentCreateRequest": { + "type": "object", + "required": [ + "agent_id", + "name", + "type" + ], + "properties": { + "agent_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "status": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, + "request.AgentUpdateRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "instructions": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "status": { + "type": "boolean" + }, + "type": { + "type": "string" + } + } + }, "request.AiChatFilesUpdateRequest": { "type": "object", "required": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 9ae5fb8..5ab2db7 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -148,6 +148,42 @@ definitions: - redirectLink - title type: object + request.AgentCreateRequest: + properties: + agent_id: + type: string + description: + type: string + instructions: + type: string + is_active: + type: boolean + name: + type: string + status: + type: boolean + type: + type: string + required: + - agent_id + - name + - type + type: object + request.AgentUpdateRequest: + properties: + description: + type: string + instructions: + type: string + is_active: + type: boolean + name: + type: string + status: + type: boolean + type: + type: string + type: object request.AiChatFilesUpdateRequest: properties: fileAlt: @@ -2014,6 +2050,114 @@ paths: summary: Viewer Advertisement tags: - Advertisement + /agent: + get: + consumes: + - application/json + description: Get list agent + parameters: + - description: Agent name + in: query + name: name + type: string + - description: Agent type + in: query + name: type + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + summary: Get list agent + tags: + - Agent + post: + consumes: + - application/json + parameters: + - description: Agent payload + in: body + name: body + required: true + schema: + $ref: '#/definitions/request.AgentCreateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + summary: Create agent + tags: + - Agent + /agent/{id}: + delete: + consumes: + - application/json + parameters: + - description: Agent ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + summary: Delete agent + tags: + - Agent + get: + consumes: + - application/json + parameters: + - description: Agent ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + summary: Get agent by ID + tags: + - Agent + put: + consumes: + - application/json + parameters: + - description: Agent ID + in: path + name: id + required: true + type: integer + - description: Agent payload + in: body + name: body + required: true + schema: + $ref: '#/definitions/request.AgentUpdateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + summary: Update agent + tags: + - Agent /ai-chat-files: get: description: API for getting all AiChatFiles @@ -11176,6 +11320,55 @@ paths: summary: EmailValidation Users tags: - Users + /users/experts: + get: + description: Get users with role expert (user_role_id = 2) + parameters: + - description: Filter by expert name + in: query + name: name + type: string + - description: Filter by expert type + in: query + name: type + type: string + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Get list expert users + tags: + - User Expert + /users/experts/{id}: + get: + description: Get expert user with work, education, and research histories + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + responses: + "200": + description: OK + schema: + $ref: '#/definitions/response.Response' + "404": + description: Not Found + schema: + $ref: '#/definitions/response.BadRequestError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + summary: Get expert user detail + tags: + - User Expert /users/forgot-password: post: description: API for ForgotPassword Users diff --git a/main.go b/main.go index d2027ad..25a0f22 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "narasi-ahli-be/app/middleware" "narasi-ahli-be/app/module/activity_logs" "narasi-ahli-be/app/module/advertisement" + "narasi-ahli-be/app/module/agent" "narasi-ahli-be/app/module/ai_chat" "narasi-ahli-be/app/module/ai_chat_files" "narasi-ahli-be/app/module/article_approvals" @@ -101,6 +102,8 @@ func main() { research_journals.NewResearchJournalsModule, ai_chat_files.NewAiChatFilesModule, notifications.NewNotificationsModule, + agent.NewAgentModule, + // start aplication