repository attachment api

This commit is contained in:
efrilm 2025-10-15 21:58:44 +07:00
parent da2246d45a
commit 58128495a6
15 changed files with 606 additions and 64 deletions

View File

@ -53,6 +53,7 @@ func (a *App) Initialize(cfg *config.Config) error {
onlyOfficeHandler := handler.NewOnlyOfficeHandler(services.onlyOfficeService) onlyOfficeHandler := handler.NewOnlyOfficeHandler(services.onlyOfficeService)
analyticsHandler := handler.NewAnalyticsHandler(services.analyticsService) analyticsHandler := handler.NewAnalyticsHandler(services.analyticsService)
notificationHandler := handler.NewNotificationHandler(services.notificationService) notificationHandler := handler.NewNotificationHandler(services.notificationService)
repositoryAttachmentHandler := handler.NewRepositoryAttachmentHandler(services.repositoryAttachmentService)
a.router = router.NewRouter( a.router = router.NewRouter(
cfg, cfg,
@ -70,6 +71,7 @@ func (a *App) Initialize(cfg *config.Config) error {
onlyOfficeHandler, onlyOfficeHandler,
analyticsHandler, analyticsHandler,
notificationHandler, notificationHandler,
repositoryAttachmentHandler,
) )
return nil return nil
@ -147,6 +149,7 @@ type repositories struct {
approvalFlowRepo *repository.ApprovalFlowRepository approvalFlowRepo *repository.ApprovalFlowRepository
letterOutgoingApprovalRepo *repository.LetterOutgoingApprovalRepository letterOutgoingApprovalRepo *repository.LetterOutgoingApprovalRepository
analyticsRepo *repository.AnalyticsRepository analyticsRepo *repository.AnalyticsRepository
repositoryAttachmentRepo *repository.RepositoryAttachmentRepositoryImpl
} }
func (a *App) initRepositories() *repositories { func (a *App) initRepositories() *repositories {
@ -181,6 +184,7 @@ func (a *App) initRepositories() *repositories {
approvalFlowRepo: repository.NewApprovalFlowRepository(a.db), approvalFlowRepo: repository.NewApprovalFlowRepository(a.db),
letterOutgoingApprovalRepo: repository.NewLetterOutgoingApprovalRepository(a.db), letterOutgoingApprovalRepo: repository.NewLetterOutgoingApprovalRepository(a.db),
analyticsRepo: repository.NewAnalyticsRepository(a.db), analyticsRepo: repository.NewAnalyticsRepository(a.db),
repositoryAttachmentRepo: repository.NewRepositoryAttachmentRepositoryImpl(a.db),
} }
} }
@ -198,13 +202,14 @@ type processors struct {
letterDispositionProcessor *processor.LetterDispositionProcessorImpl letterDispositionProcessor *processor.LetterDispositionProcessorImpl
letterDispositionDeptProcessor *processor.LetterDispositionDepartmentProcessorImpl letterDispositionDeptProcessor *processor.LetterDispositionDepartmentProcessorImpl
// Modular processors for letter outgoing // Modular processors for letter outgoing
letterValidationProcessor processor.LetterValidationProcessor letterValidationProcessor processor.LetterValidationProcessor
letterCreationProcessor processor.LetterCreationProcessor letterCreationProcessor processor.LetterCreationProcessor
letterApprovalProcessor processor.LetterApprovalProcessor letterApprovalProcessor processor.LetterApprovalProcessor
letterAttachmentProcessor processor.LetterAttachmentProcessor letterAttachmentProcessor processor.LetterAttachmentProcessor
letterOutgoingRecipientProcessor processor.LetterOutgoingRecipientProcessor letterOutgoingRecipientProcessor processor.LetterOutgoingRecipientProcessor
letterActivityProcessor processor.LetterActivityProcessor letterActivityProcessor processor.LetterActivityProcessor
txManager *repository.TxManager repositoryAttachmentProcessor *processor.RepositoryAttachmentProcessorImpl
txManager *repository.TxManager
} }
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors { func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
@ -327,6 +332,9 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
repos.letterRepo, repos.letterRepo,
) )
repositoryAttachmentProc := processor.NewRepositoryAttachmentProcessor(
repos.repositoryAttachmentRepo)
return &processors{ return &processors{
userProcessor: userProc, userProcessor: userProc,
cachedUserProcessor: cachedUserProc, cachedUserProcessor: cachedUserProc,
@ -347,23 +355,25 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
letterAttachmentProcessor: letterAttachmentProc, letterAttachmentProcessor: letterAttachmentProc,
letterOutgoingRecipientProcessor: letterOutgoingRecipientProc, letterOutgoingRecipientProcessor: letterOutgoingRecipientProc,
letterActivityProcessor: letterActivityProc, letterActivityProcessor: letterActivityProc,
repositoryAttachmentProcessor: repositoryAttachmentProc,
txManager: txMgr, txManager: txMgr,
} }
} }
type services struct { type services struct {
userService *service.UserServiceImpl userService *service.UserServiceImpl
authService *service.AuthServiceImpl authService *service.AuthServiceImpl
fileService *service.FileServiceImpl fileService *service.FileServiceImpl
rbacService *service.RBACServiceImpl rbacService *service.RBACServiceImpl
masterService *service.MasterServiceImpl masterService *service.MasterServiceImpl
letterService *service.LetterServiceImpl letterService *service.LetterServiceImpl
letterOutgoingService *service.LetterOutgoingServiceImpl letterOutgoingService *service.LetterOutgoingServiceImpl
approvalFlowService *service.ApprovalFlowServiceImpl approvalFlowService *service.ApprovalFlowServiceImpl
dispositionRouteService *service.DispositionRouteServiceImpl dispositionRouteService *service.DispositionRouteServiceImpl
onlyOfficeService *service.OnlyOfficeServiceImpl onlyOfficeService *service.OnlyOfficeServiceImpl
analyticsService *service.AnalyticsServiceImpl analyticsService *service.AnalyticsServiceImpl
notificationService *service.NotificationServiceImpl notificationService *service.NotificationServiceImpl
repositoryAttachmentService *service.RepositoryAttachmentServiceImpl
} }
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services { func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
@ -423,19 +433,22 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
novuConfig := internalConfig.LoadNovuConfig(cfg) novuConfig := internalConfig.LoadNovuConfig(cfg)
notificationSvc := service.NewNotificationService(novuConfig, processors.userProcessor) notificationSvc := service.NewNotificationService(novuConfig, processors.userProcessor)
repositoryAttachmentSvc := service.NewRepositoryAttachmentService(processors.repositoryAttachmentProcessor)
return &services{ return &services{
userService: userSvc, userService: userSvc,
authService: authService, authService: authService,
fileService: fileSvc, fileService: fileSvc,
rbacService: rbacSvc, rbacService: rbacSvc,
masterService: masterSvc, masterService: masterSvc,
letterService: letterSvc, letterService: letterSvc,
letterOutgoingService: letterOutgoingSvc, letterOutgoingService: letterOutgoingSvc,
approvalFlowService: approvalFlowSvc, approvalFlowService: approvalFlowSvc,
dispositionRouteService: dispRouteSvc, dispositionRouteService: dispRouteSvc,
onlyOfficeService: onlyOfficeSvc, onlyOfficeService: onlyOfficeSvc,
analyticsService: analyticsSvc, analyticsService: analyticsSvc,
notificationService: notificationSvc, notificationService: notificationSvc,
repositoryAttachmentService: repositoryAttachmentSvc,
} }
} }

View File

@ -0,0 +1,35 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateRepositoryAttachmentRequest struct {
FileURL string `json:"file_url" validate:"required"`
FileName string `json:"file_name" validate:"required"`
FileType string `json:"file_type" validate:"required"`
Category string `json:"category" validate:"required"`
}
type RepositoryAttachmentsResponse struct {
ID uuid.UUID `json:"id"`
FileURL string `json:"file_url"`
FileName string `json:"file_name"`
FileType string `json:"file_type"`
Category string `json:"category"`
UploadBy uuid.UUID `json:"upload_by"`
UploadAt time.Time `json:"upload_at"`
}
type ListRepositoryAttachmentsResponse struct {
Attachments []RepositoryAttachmentsResponse `json:"attachments"`
Pagination PaginationResponse `json:"pagination"`
}
type ListRepositoryAttachmentsRequest struct {
Page int `json:"page"`
Limit int `json:"limit"`
Search *string `json:"search,omitempty"`
}

View File

@ -0,0 +1,19 @@
package entities
import (
"time"
"github.com/google/uuid"
)
type RepositoryAttachment struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
FileURL string `gorm:"not null" json:"file_url"`
FileName string `gorm:"not null" json:"file_name"`
FileType string `gorm:"not null" json:"file_type"`
Category string `gorm:"not null" json:"category"`
UploadedBy *uuid.UUID `json:"uploaded_by,omitempty"`
UploadedAt time.Time `gorm:"autoCreateTime" json:"uploaded_at"`
}
func (RepositoryAttachment) TableName() string { return "repository_attachments" }

View File

@ -0,0 +1,137 @@
package handler
import (
"eslogad-be/internal/constants"
"eslogad-be/internal/contract"
"eslogad-be/internal/logger"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type RepositoryAttachmentHandler struct {
attachmentService RepositoryAttachmentService
}
func NewRepositoryAttachmentHandler(attachmentService RepositoryAttachmentService) *RepositoryAttachmentHandler {
return &RepositoryAttachmentHandler{
attachmentService: attachmentService,
}
}
func (h *RepositoryAttachmentHandler) CreateAttachment(c *gin.Context) {
var req contract.CreateRepositoryAttachmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::CreateAttachment -> request binding failed")
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
userResponse, err := h.attachmentService.CreateAttachment(c.Request.Context(), &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::CreateAttachment -> Failed to create user from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::CreateUser -> Successfully created repository attachment = %+v", userResponse)
c.JSON(http.StatusOK, contract.BuildSuccessResponse(userResponse))
}
func (h *RepositoryAttachmentHandler) DeleteAttachment(c *gin.Context) {
attachmentIDStr := c.Param("id")
attachmentID, err := uuid.Parse(attachmentIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::DeleteAttachment -> Invalid attachment id")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
err = h.attachmentService.DeleteAttachment(c.Request.Context(), attachmentID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::DeleteAttachment -> Failed to delete attachment from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Info("UserHandler::DeleteAttachment -> Successfully deleted attachment")
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "User deleted successfully"})
}
func (h *RepositoryAttachmentHandler) GetAttachment(c *gin.Context) {
attachmentIDStr := c.Param("id")
attachmentID, err := uuid.Parse(attachmentIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetAttachment -> Invalid attachment ID")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
attachmentResponse, err := h.attachmentService.GetById(c.Request.Context(), attachmentID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetAttachment -> Failed to get attachment from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::GetAttachment -> Successfully retrieved attachment = %+v", attachmentResponse)
c.JSON(http.StatusOK, attachmentResponse)
}
func (h *RepositoryAttachmentHandler) ListAttachment(c *gin.Context) {
ctx := c.Request.Context()
req := &contract.ListRepositoryAttachmentsRequest{
Page: 1,
Limit: 10,
}
if page := c.Query("page"); page != "" {
if p, err := strconv.Atoi(page); err == nil {
req.Page = p
}
}
if limit := c.Query("limit"); limit != "" {
if l, err := strconv.Atoi(limit); err == nil {
req.Limit = l
}
}
attachmentsResponse, err := h.attachmentService.ListAttachment(ctx, req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ListUsers -> Failed to list users from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::ListUsers -> Successfully listed users = %+v", attachmentsResponse)
c.JSON(http.StatusOK, contract.BuildSuccessResponse(attachmentsResponse))
}
func (h *RepositoryAttachmentHandler) sendValidationErrorResponse(c *gin.Context, message string, errorCode string) {
statusCode := constants.HttpErrorMap[errorCode]
if statusCode == 0 {
statusCode = http.StatusBadRequest
}
errorResponse := &contract.ErrorResponse{
Error: message,
Code: statusCode,
Details: map[string]interface{}{
"error_code": errorCode,
"entity": constants.UserValidatorEntity,
},
}
c.JSON(statusCode, errorResponse)
}
func (h *RepositoryAttachmentHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) {
errorResponse := &contract.ErrorResponse{
Error: message,
Code: statusCode,
Details: map[string]interface{}{},
}
c.JSON(statusCode, errorResponse)
}

View File

@ -0,0 +1,15 @@
package handler
import (
"context"
"eslogad-be/internal/contract"
"github.com/google/uuid"
)
type RepositoryAttachmentService interface {
CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error)
DeleteAttachment(ctx context.Context, id uuid.UUID) error
GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error)
ListAttachment(ctx context.Context, req *contract.ListRepositoryAttachmentsRequest) (*contract.ListRepositoryAttachmentsResponse, error)
}

View File

@ -0,0 +1,78 @@
package processor
import (
"context"
"eslogad-be/internal/appcontext"
"eslogad-be/internal/contract"
"eslogad-be/internal/transformer"
"fmt"
"github.com/google/uuid"
)
type RepositoryAttachmentProcessorImpl struct {
attachmentRepo RepositoryAttachmentRepository
}
func NewRepositoryAttachmentProcessor(attachmentRepo RepositoryAttachmentRepository) *RepositoryAttachmentProcessorImpl {
return &RepositoryAttachmentProcessorImpl{
attachmentRepo: attachmentRepo,
}
}
func (p *RepositoryAttachmentProcessorImpl) CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error) {
userID := getUserIDFromContext(ctx)
attachmentEntity := transformer.CreateRepositoryAttachmentRequestToEntity(req, userID)
err := p.attachmentRepo.Create(ctx, attachmentEntity)
if err != nil {
return nil, fmt.Errorf("failed to create repository attachment: %w", err)
}
return transformer.RepositoryAttachmentEntityToContract(attachmentEntity), nil
}
func getUserIDFromContext(ctx context.Context) uuid.UUID {
appCtx := appcontext.FromGinContext(ctx)
if appCtx != nil {
return appCtx.UserID
}
return uuid.New()
}
func (p *RepositoryAttachmentProcessorImpl) DeleteAttachment(ctx context.Context, id uuid.UUID) error {
_, err := p.attachmentRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("repository attachment not found: %w", err)
}
err = p.attachmentRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete repository attachment: %w", err)
}
return nil
}
func (p *RepositoryAttachmentProcessorImpl) GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error) {
attachment, err := p.attachmentRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("repository attachment not found: %w", err)
}
resp := transformer.RepositoryAttachmentEntityToContract(attachment)
return resp, nil
}
func (p *RepositoryAttachmentProcessorImpl) ListAttachment(ctx context.Context, search *string, limit, offset int) ([]contract.RepositoryAttachmentsResponse, int, error) {
attachments, totalCount, err := p.attachmentRepo.List(ctx, search, limit, offset)
if err != nil {
return nil, 0, fmt.Errorf("failed to get users: %w", err)
}
responses := transformer.RepositoryAttachmentEntityToContracts(attachments)
return responses, int(totalCount), nil
}

View File

@ -0,0 +1,16 @@
package processor
import (
"context"
"eslogad-be/internal/entities"
"github.com/google/uuid"
)
type RepositoryAttachmentRepository interface {
Create(ctx context.Context, user *entities.RepositoryAttachment) error
GetByID(ctx context.Context, id uuid.UUID) (*entities.RepositoryAttachment, error)
Update(ctx context.Context, user *entities.RepositoryAttachment) error
Delete(ctx context.Context, id uuid.UUID) error
List(ctx context.Context, search *string, limit, offset int) ([]*entities.RepositoryAttachment, int64, error)
}

View File

@ -0,0 +1,74 @@
package repository
import (
"context"
"eslogad-be/internal/entities"
"github.com/google/uuid"
"gorm.io/gorm"
)
type RepositoryAttachmentRepositoryImpl struct {
b *gorm.DB
}
func NewRepositoryAttachmentRepositoryImpl(db *gorm.DB) *RepositoryAttachmentRepositoryImpl {
return &RepositoryAttachmentRepositoryImpl{
b: db,
}
}
func (r *RepositoryAttachmentRepositoryImpl) Create(ctx context.Context, user *entities.RepositoryAttachment) error {
return r.b.WithContext(ctx).Create(user).Error
}
func (r *RepositoryAttachmentRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.RepositoryAttachment, error) {
var attachment entities.RepositoryAttachment
err := r.b.WithContext(ctx).
First(&attachment, "id = ?", id).Error
if err != nil {
return nil, err
}
return &attachment, nil
}
func (r *RepositoryAttachmentRepositoryImpl) Update(ctx context.Context, user *entities.RepositoryAttachment) error {
return r.b.WithContext(ctx).Save(user).Error
}
func (r *RepositoryAttachmentRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
return r.b.WithContext(ctx).Delete(&entities.RepositoryAttachment{}, "id = ?", id).Error
}
func (r *RepositoryAttachmentRepositoryImpl) List(ctx context.Context, search *string, limit, offset int) ([]*entities.RepositoryAttachment, int64, error) {
var attachments []*entities.RepositoryAttachment
var total int64
baseQuery := r.b.WithContext(ctx).Model(&entities.RepositoryAttachment{})
if search != nil && *search != "" {
like := "%" + *search + "%"
baseQuery = baseQuery.Where("name ILIKE ? OR email ILIKE ?", like, like)
}
countQuery := baseQuery
if err := countQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
dataQuery := r.b.WithContext(ctx).Model(&entities.RepositoryAttachment{})
if search != nil && *search != "" {
like := "%" + *search + "%"
dataQuery = dataQuery.Where("name ILIKE ? OR category ILIKE ?", like, like)
}
if err := dataQuery.
Limit(limit).
Offset(offset).
Find(&attachments).Error; err != nil {
return nil, 0, err
}
return attachments, total, nil
}

View File

@ -180,3 +180,10 @@ type NotificationHandler interface {
GetCurrentUserSubscriber(c *gin.Context) GetCurrentUserSubscriber(c *gin.Context)
UpdateCurrentUserSubscriberChannel(c *gin.Context) UpdateCurrentUserSubscriberChannel(c *gin.Context)
} }
type RepositoryAttachmentHandler interface {
CreateAttachment(c *gin.Context)
DeleteAttachment(c *gin.Context)
GetAttachment(c *gin.Context)
ListAttachment(c *gin.Context)
}

View File

@ -8,21 +8,22 @@ import (
) )
type Router struct { type Router struct {
config *config.Config config *config.Config
authHandler AuthHandler authHandler AuthHandler
healthHandler HealthHandler healthHandler HealthHandler
authMiddleware AuthMiddleware authMiddleware AuthMiddleware
userHandler UserHandler userHandler UserHandler
fileHandler FileHandler fileHandler FileHandler
rbacHandler RBACHandler rbacHandler RBACHandler
masterHandler MasterHandler masterHandler MasterHandler
letterHandler LetterHandler letterHandler LetterHandler
letterOutgoingHandler LetterOutgoingHandler letterOutgoingHandler LetterOutgoingHandler
adminApprovalFlowHandler AdminApprovalFlowHandler adminApprovalFlowHandler AdminApprovalFlowHandler
dispRouteHandler DispositionRouteHandler dispRouteHandler DispositionRouteHandler
onlyOfficeHandler OnlyOfficeHandler onlyOfficeHandler OnlyOfficeHandler
analyticsHandler AnalyticsHandler analyticsHandler AnalyticsHandler
notificationHandler NotificationHandler notificationHandler NotificationHandler
repositoryAttachmentHandler RepositoryAttachmentHandler
} }
func NewRouter( func NewRouter(
@ -41,23 +42,25 @@ func NewRouter(
onlyOfficeHandler OnlyOfficeHandler, onlyOfficeHandler OnlyOfficeHandler,
analyticsHandler AnalyticsHandler, analyticsHandler AnalyticsHandler,
notificationHandler NotificationHandler, notificationHandler NotificationHandler,
repositoryAttachmentHandler RepositoryAttachmentHandler,
) *Router { ) *Router {
return &Router{ return &Router{
config: cfg, config: cfg,
authHandler: authHandler, authHandler: authHandler,
authMiddleware: authMiddleware, authMiddleware: authMiddleware,
healthHandler: healthHandler, healthHandler: healthHandler,
userHandler: userHandler, userHandler: userHandler,
fileHandler: fileHandler, fileHandler: fileHandler,
rbacHandler: rbacHandler, rbacHandler: rbacHandler,
masterHandler: masterHandler, masterHandler: masterHandler,
letterHandler: letterHandler, letterHandler: letterHandler,
letterOutgoingHandler: letterOutgoingHandler, letterOutgoingHandler: letterOutgoingHandler,
adminApprovalFlowHandler: adminApprovalFlowHandler, adminApprovalFlowHandler: adminApprovalFlowHandler,
dispRouteHandler: dispRouteHandler, dispRouteHandler: dispRouteHandler,
onlyOfficeHandler: onlyOfficeHandler, onlyOfficeHandler: onlyOfficeHandler,
analyticsHandler: analyticsHandler, analyticsHandler: analyticsHandler,
notificationHandler: notificationHandler, notificationHandler: notificationHandler,
repositoryAttachmentHandler: repositoryAttachmentHandler,
} }
} }
@ -236,6 +239,15 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
droutes.PUT("/:id/active", r.dispRouteHandler.SetActive) droutes.PUT("/:id/active", r.dispRouteHandler.SetActive)
} }
repoattachsch := v1.Group("/repository-attachments")
repoattachsch.Use(r.authMiddleware.RequireAuth())
{
repoattachsch.POST("", r.repositoryAttachmentHandler.CreateAttachment)
repoattachsch.GET("", r.repositoryAttachmentHandler.ListAttachment)
repoattachsch.DELETE("/:id", r.repositoryAttachmentHandler.DeleteAttachment)
repoattachsch.GET("/:id", r.repositoryAttachmentHandler.GetAttachment)
}
admin := v1.Group("/setting") admin := v1.Group("/setting")
admin.Use(r.authMiddleware.RequireAuth()) admin.Use(r.authMiddleware.RequireAuth())
{ {

View File

@ -0,0 +1,15 @@
package service
import (
"context"
"eslogad-be/internal/contract"
"github.com/google/uuid"
)
type RepositoryAttachmentProcessor interface {
CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error)
DeleteAttachment(ctx context.Context, id uuid.UUID) error
GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error)
ListAttachment(ctx context.Context, search *string, limit, offset int) ([]contract.RepositoryAttachmentsResponse, int, error)
}

View File

@ -0,0 +1,59 @@
package service
import (
"context"
"eslogad-be/internal/contract"
"eslogad-be/internal/transformer"
"github.com/google/uuid"
)
type RepositoryAttachmentServiceImpl struct {
attachmentProcessor RepositoryAttachmentProcessor
}
func NewRepositoryAttachmentService(attachmentProcessor RepositoryAttachmentProcessor) *RepositoryAttachmentServiceImpl {
return &RepositoryAttachmentServiceImpl{
attachmentProcessor: attachmentProcessor,
}
}
func (s *RepositoryAttachmentServiceImpl) CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error) {
return s.attachmentProcessor.CreateAttachment(ctx, req)
}
func (s *RepositoryAttachmentServiceImpl) DeleteAttachment(ctx context.Context, id uuid.UUID) error {
return s.attachmentProcessor.DeleteAttachment(ctx, id)
}
func (s *RepositoryAttachmentServiceImpl) GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error) {
return s.attachmentProcessor.GetById(ctx, id)
}
func (s *RepositoryAttachmentServiceImpl) ListAttachment(ctx context.Context, req *contract.ListRepositoryAttachmentsRequest) (*contract.ListRepositoryAttachmentsResponse, error) {
page := req.Page
if page <= 0 {
page = 1
}
limit := req.Limit
if limit <= 0 {
limit = 10
}
if limit > 100 {
limit = 100 // Max limit to prevent performance issues
}
offset := (page - 1) * limit
// Pass calculated offset and limit to processor
attachmentResponses, totalCount, err := s.attachmentProcessor.ListAttachment(ctx, req.Search, limit, offset)
if err != nil {
return nil, err
}
return &contract.ListRepositoryAttachmentsResponse{
Attachments: attachmentResponses,
Pagination: transformer.CreatePaginationResponse(totalCount, page, limit),
}, nil
}

View File

@ -0,0 +1,49 @@
package transformer
import (
"eslogad-be/internal/contract"
"eslogad-be/internal/entities"
"github.com/google/uuid"
)
func CreateRepositoryAttachmentRequestToEntity(req *contract.CreateRepositoryAttachmentRequest, userId uuid.UUID) *entities.RepositoryAttachment {
if req == nil {
return nil
}
return &entities.RepositoryAttachment{
FileName: req.FileName,
FileType: req.FileType,
FileURL: req.FileURL,
UploadedBy: &userId,
Category: req.Category,
}
}
func RepositoryAttachmentEntityToContract(entity *entities.RepositoryAttachment) *contract.RepositoryAttachmentsResponse {
resp := &contract.RepositoryAttachmentsResponse{
ID: entity.ID,
FileName: entity.FileName,
FileType: entity.FileType,
FileURL: entity.FileURL,
Category: entity.Category,
UploadBy: *entity.UploadedBy,
UploadAt: entity.UploadedAt,
}
return resp
}
func RepositoryAttachmentEntityToContracts(attachments []*entities.RepositoryAttachment) []contract.RepositoryAttachmentsResponse {
if attachments == nil {
return nil
}
responses := make([]contract.RepositoryAttachmentsResponse, len(attachments))
for i, u := range attachments {
resp := RepositoryAttachmentEntityToContract(u)
if resp != nil {
responses[i] = *resp
}
}
return responses
}

View File

@ -0,0 +1,13 @@
BEGIN;
CREATE TABLE IF NOT EXISTS repository_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_url TEXT NOT NULL,
file_name TEXT NOT NULL,
file_type TEXT NOT NULL,
category TEXT NOT NULL,
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
COMMIT;