21 KiB
21 KiB
Code Templates for Narasi Ahli Implementation
Database Entity Template
Example: Education History Entity
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
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
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 X-Client-Key header string false "Insert the X-Client-Key"
// @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 X-Client-Key header string false "Insert the X-Client-Key"
// @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-Client-Key header string false "Insert the X-Client-Key"
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param payload body request.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
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
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
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
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
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
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.