# Code Templates for Narasi Ahli Implementation ## Database Entity Template ### Example: Education History Entity ```go package entity import ( "narasi-ahli-be/app/database/entity/users" "time" ) type EducationHistory struct { ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` UserID uint `json:"user_id" gorm:"type:int4;not null;index"` SchoolName string `json:"school_name" gorm:"type:varchar(255);not null"` Major string `json:"major" gorm:"type:varchar(255);not null"` EducationLevel string `json:"education_level" gorm:"type:varchar(100);not null"` GraduationYear int `json:"graduation_year" gorm:"type:int4;not null"` CertificateImage *string `json:"certificate_image" gorm:"type:varchar(500)"` CreatedAt time.Time `json:"created_at" gorm:"default:now()"` UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"` // Relationships User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"` } func (EducationHistory) TableName() string { return "education_histories" } ``` ## Module Structure Template ### Module File Template ```go package education_history import ( "narasi-ahli-be/app/module/education_history/controller" "narasi-ahli-be/app/module/education_history/repository" "narasi-ahli-be/app/module/education_history/service" "github.com/gofiber/fiber/v2" "go.uber.org/fx" ) type EducationHistoryRouter struct { App fiber.Router Controller *controller.Controller } var NewEducationHistoryModule = fx.Options( fx.Provide(repository.NewEducationHistoryRepository), fx.Provide(service.NewEducationHistoryService), fx.Provide(controller.NewController), fx.Provide(NewEducationHistoryRouter), ) func NewEducationHistoryRouter(fiber *fiber.App, controller *controller.Controller) *EducationHistoryRouter { return &EducationHistoryRouter{ App: fiber, Controller: controller, } } func (_i *EducationHistoryRouter) RegisterEducationHistoryRoutes() { educationController := _i.Controller.EducationHistory _i.App.Route("/education-history", func(router fiber.Router) { router.Get("/", educationController.All) router.Get("/:id", educationController.Show) router.Post("/", educationController.Save) router.Put("/:id", educationController.Update) router.Delete("/:id", educationController.Delete) router.Post("/:id/certificate", educationController.UploadCertificate) }) } ``` ### Controller Template ```go package controller import ( "narasi-ahli-be/app/middleware" "narasi-ahli-be/app/module/education_history/request" "narasi-ahli-be/app/module/education_history/service" "narasi-ahli-be/utils/paginator" utilRes "narasi-ahli-be/utils/response" utilVal "narasi-ahli-be/utils/validator" utilSvc "narasi-ahli-be/utils/service" "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 authenticated user // @Tags Education History // @Security Bearer // @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 401 {object} response.UnauthorizedError // @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 } // Get user ID from auth token authHeader := c.Get("Authorization") userId := utilSvc.GetUserId(authHeader) if userId == nil { return utilRes.Resp(c, utilRes.Response{ Success: false, Code: 401, Messages: utilRes.Messages{"Unauthorized"}, }) } reqContext := request.EducationHistoryQueryRequestContext{ SchoolName: c.Query("schoolName"), Major: c.Query("major"), EducationLevel: c.Query("educationLevel"), GraduationYear: c.Query("graduationYear"), } req := reqContext.ToParamRequest() req.Pagination = paginate clientId := middleware.GetClientID(c) educationData, paging, err := _i.educationHistoryService.All(clientId, *userId, 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 // @Security Bearer // @Param id path int true "Education History ID" // @Success 200 {object} response.Response // @Failure 400 {object} response.BadRequestError // @Failure 401 {object} response.UnauthorizedError // @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 } authHeader := c.Get("Authorization") userId := utilSvc.GetUserId(authHeader) if userId == nil { return utilRes.Resp(c, utilRes.Response{ Success: false, Code: 401, Messages: utilRes.Messages{"Unauthorized"}, }) } clientId := middleware.GetClientID(c) educationData, err := _i.educationHistoryService.Show(clientId, *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 // @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.EducationHistoryCreateRequest true "Required payload" // @Success 200 {object} response.Response // @Failure 400 {object} response.BadRequestError // @Failure 401 {object} response.UnauthorizedError // @Failure 500 {object} response.InternalServerError // @Router /education-history [post] func (_i *educationHistoryController) Save(c *fiber.Ctx) error { req := new(request.EducationHistoryCreateRequest) if err := utilVal.ParseAndValidate(c, req); err != nil { return err } authHeader := c.Get("Authorization") userId := utilSvc.GetUserId(authHeader) if userId == nil { return utilRes.Resp(c, utilRes.Response{ Success: false, Code: 401, Messages: utilRes.Messages{"Unauthorized"}, }) } clientId := middleware.GetClientID(c) dataResult, err := _i.educationHistoryService.Save(clientId, *userId, *req) if err != nil { return err } return utilRes.Resp(c, utilRes.Response{ Success: true, Messages: utilRes.Messages{"Education history successfully created"}, Data: dataResult, }) } // Additional methods: Update, Delete, UploadCertificate... ``` ### Service Template ```go package service import ( "narasi-ahli-be/app/module/education_history/mapper" "narasi-ahli-be/app/module/education_history/repository" "narasi-ahli-be/app/module/education_history/request" "narasi-ahli-be/app/module/education_history/response" "narasi-ahli-be/utils/paginator" "github.com/google/uuid" "github.com/rs/zerolog" ) type educationHistoryService struct { Repo repository.EducationHistoryRepository Log zerolog.Logger } type EducationHistoryService interface { All(clientId *uuid.UUID, userId uint, req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) Show(clientId *uuid.UUID, userId uint, id uint) (educationHistory *response.EducationHistoryResponse, err error) Save(clientId *uuid.UUID, userId uint, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) Update(clientId *uuid.UUID, userId uint, id uint, req request.EducationHistoryUpdateRequest) (err error) Delete(clientId *uuid.UUID, userId uint, id uint) error UploadCertificate(clientId *uuid.UUID, userId uint, id uint, filePath string) error } func NewEducationHistoryService(repo repository.EducationHistoryRepository, log zerolog.Logger) EducationHistoryService { return &educationHistoryService{ Repo: repo, Log: log, } } func (_i *educationHistoryService) All(clientId *uuid.UUID, userId uint, req request.EducationHistoryQueryRequest) (educationHistories []*response.EducationHistoryResponse, paging paginator.Pagination, err error) { results, paging, err := _i.Repo.GetAll(clientId, userId, req) if err != nil { return } for _, result := range results { educationHistories = append(educationHistories, mapper.EducationHistoryResponseMapper(result)) } return } func (_i *educationHistoryService) Show(clientId *uuid.UUID, userId uint, id uint) (educationHistory *response.EducationHistoryResponse, err error) { result, err := _i.Repo.FindOneByUserAndId(clientId, userId, id) if err != nil { return nil, err } return mapper.EducationHistoryResponseMapper(result), nil } func (_i *educationHistoryService) Save(clientId *uuid.UUID, userId uint, req request.EducationHistoryCreateRequest) (educationHistory *response.EducationHistoryResponse, err error) { _i.Log.Info().Interface("data", req).Msg("Creating education history") entity := req.ToEntity() entity.UserID = userId result, err := _i.Repo.Create(clientId, entity) if err != nil { return nil, err } return mapper.EducationHistoryResponseMapper(result), nil } // Additional methods: Update, Delete, UploadCertificate... ``` ### Repository Template ```go package repository import ( "narasi-ahli-be/app/database/entity" "narasi-ahli-be/app/module/education_history/request" "narasi-ahli-be/utils/paginator" "github.com/google/uuid" "gorm.io/gorm" ) type educationHistoryRepository struct { DB *gorm.DB } type EducationHistoryRepository interface { GetAll(clientId *uuid.UUID, userId uint, req request.EducationHistoryQueryRequest) (educationHistories []*entity.EducationHistory, paging paginator.Pagination, err error) FindOneByUserAndId(clientId *uuid.UUID, userId uint, id uint) (educationHistory *entity.EducationHistory, err error) Create(clientId *uuid.UUID, educationHistory *entity.EducationHistory) (result *entity.EducationHistory, err error) Update(clientId *uuid.UUID, userId uint, id uint, educationHistory *entity.EducationHistory) (err error) Delete(clientId *uuid.UUID, userId uint, id uint) (err error) } func NewEducationHistoryRepository(db *gorm.DB) EducationHistoryRepository { return &educationHistoryRepository{ DB: db, } } func (_i *educationHistoryRepository) GetAll(clientId *uuid.UUID, userId uint, req request.EducationHistoryQueryRequest) (educationHistories []*entity.EducationHistory, paging paginator.Pagination, err error) { query := _i.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") // Apply pagination paging = paginator.Paging(req.Pagination, query, &educationHistories) return } func (_i *educationHistoryRepository) FindOneByUserAndId(clientId *uuid.UUID, userId uint, id uint) (educationHistory *entity.EducationHistory, err error) { err = _i.DB.Where("user_id = ? AND id = ?", userId, id).Preload("User").First(&educationHistory).Error return } func (_i *educationHistoryRepository) Create(clientId *uuid.UUID, educationHistory *entity.EducationHistory) (result *entity.EducationHistory, err error) { err = _i.DB.Create(educationHistory).Error if err != nil { return nil, err } // Reload with relationships err = _i.DB.Preload("User").First(&result, educationHistory.ID).Error return } // Additional methods: Update, Delete... ``` ### Request DTO Template ```go package request import ( "narasi-ahli-be/app/database/entity" "narasi-ahli-be/utils/paginator" "strconv" ) type EducationHistoryQueryRequest struct { 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() *entity.EducationHistory { return &entity.EducationHistory{ SchoolName: req.SchoolName, Major: req.Major, EducationLevel: req.EducationLevel, GraduationYear: req.GraduationYear, CertificateImage: &req.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() *entity.EducationHistory { return &entity.EducationHistory{ SchoolName: req.SchoolName, Major: req.Major, EducationLevel: req.EducationLevel, GraduationYear: req.GraduationYear, CertificateImage: &req.CertificateImage, } } type EducationHistoryQueryRequestContext struct { 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 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 } ``` ### Response DTO Template ```go 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"` // Nested user info (optional) User *UserBasicInfo `json:"user,omitempty"` } type UserBasicInfo struct { ID uint `json:"id"` Username string `json:"username"` Fullname string `json:"fullname"` Email string `json:"email"` } ``` ### Mapper Template ```go package mapper import ( "narasi-ahli-be/app/database/entity" "narasi-ahli-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, } // Include user info if loaded 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 } ``` ## File Upload Service Template ```go package service import ( "context" "fmt" "mime/multipart" "path/filepath" "strings" "time" "github.com/google/uuid" "github.com/minio/minio-go/v7" ) type FileUploadService interface { UploadCertificate(file *multipart.FileHeader) (string, error) UploadChatFile(file *multipart.FileHeader, messageType string) (string, error) ValidateFile(file *multipart.FileHeader, allowedTypes []string, maxSize int64) error } type fileUploadService struct { minioClient *minio.Client bucketName string } func NewFileUploadService(minioClient *minio.Client, bucketName string) FileUploadService { return &fileUploadService{ minioClient: minioClient, bucketName: bucketName, } } func (s *fileUploadService) UploadCertificate(file *multipart.FileHeader) (string, error) { // Validate file allowedTypes := []string{".jpg", ".jpeg", ".png", ".pdf"} maxSize := int64(5 * 1024 * 1024) // 5MB if err := s.ValidateFile(file, allowedTypes, maxSize); err != nil { return "", err } // Generate unique filename ext := filepath.Ext(file.Filename) filename := fmt.Sprintf("certificates/%s_%d%s", uuid.New().String(), time.Now().Unix(), ext) // Upload to MinIO src, err := file.Open() if err != nil { return "", err } defer src.Close() _, err = s.minioClient.PutObject( context.Background(), s.bucketName, filename, src, file.Size, minio.PutObjectOptions{ContentType: file.Header.Get("Content-Type")}, ) if err != nil { return "", err } return filename, nil } func (s *fileUploadService) ValidateFile(file *multipart.FileHeader, allowedTypes []string, maxSize int64) error { // Check file size if file.Size > maxSize { return fmt.Errorf("file size exceeds maximum allowed size") } // Check file type ext := strings.ToLower(filepath.Ext(file.Filename)) allowed := false for _, allowedType := range allowedTypes { if ext == allowedType { allowed = true break } } if !allowed { return fmt.Errorf("file type not allowed") } return nil } ``` These templates provide a solid foundation for implementing each module following the existing codebase patterns. Each module can be built by copying and adapting these templates with the specific field requirements for that module.