package handlers import ( "fmt" "mime/multipart" "net/http" "os" "path/filepath" "strconv" "strings" "time" "gauth-central/config" "gauth-central/internal/services" "gauth-central/pkg/utils" "github.com/gin-gonic/gin" ) type AboutHandler struct { aboutService *services.AboutService } func NewAboutHandler(aboutService *services.AboutService) *AboutHandler { return &AboutHandler{aboutService: aboutService} } // GetAllAbout godoc // @Summary Get all active about entries // @Description Retrieve a list of active about entries // @Tags about // @Produce json // @Success 200 {array} models.About // @Failure 500 {object} map[string]string // @Router /about [get] func (h *AboutHandler) GetAllAbout(c *gin.Context) { abouts, err := h.aboutService.GetAllAbout(true) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, abouts) } // GetActiveAbout godoc // @Summary Get active about entry // @Description Retrieve the newest active about entry // @Tags about // @Produce json // @Success 200 {object} models.About // @Failure 404 {object} map[string]string // @Router /about/active [get] func (h *AboutHandler) GetActiveAbout(c *gin.Context) { about, err := h.aboutService.GetFirstActiveAbout() if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, about) } // AdminGetAllAbout godoc // @Summary Get all about entries (Admin) // @Description Retrieve a list of all about entries including inactive ones // @Tags admin // @Security ApiKeyAuth // @Produce json // @Success 200 {array} models.About // @Failure 500 {object} map[string]string // @Router /admin/about [get] func (h *AboutHandler) AdminGetAllAbout(c *gin.Context) { abouts, err := h.aboutService.GetAllAbout(false) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, abouts) } // AdminGetAboutByID godoc // @Summary Get an about entry by ID (Admin) // @Description Retrieve details of a specific about entry // @Tags admin // @Security ApiKeyAuth // @Produce json // @Param id path string true "About ID" // @Success 200 {object} models.About // @Failure 404 {object} map[string]string // @Router /admin/about/{id} [get] func (h *AboutHandler) AdminGetAboutByID(c *gin.Context) { id := c.Param("id") about, err := h.aboutService.GetAboutByID(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, about) } // CreateAbout godoc // @Summary Create a new about entry (Admin) // @Description Create a new about entry // @Tags admin // @Security ApiKeyAuth // @Accept multipart/form-data // @Produce json // @Param title formData string true "Title" // @Param image formData file false "Image" // @Param image_sub formData string false "Image subtitle" // @Param cv formData file false "CV file" // @Param birthday formData string false "Birthday" // @Param city formData string false "City" // @Param study formData string false "Study" // @Param website formData string false "Website" // @Param phone formData string false "Phone" // @Param age formData string false "Age" // @Param interests formData string false "Interests" // @Param degree formData string false "Degree" // @Param x formData string false "X" // @Param mail formData string false "Mail" // @Param done formData int false "Done" // @Param project_done formData string false "Project done" // @Param user_h formData int false "User count" // @Param hapy_user formData string false "Happy user" // @Param great formData int false "Great" // @Param great_reviews formData string false "Great reviews" // @Param team formData int false "Team" // @Param support_team formData string false "Support team" // @Param is_active formData bool false "Is active" // @Param counter_active formData bool false "Counter active" // @Success 201 {object} models.About // @Failure 400 {object} map[string]string // @Router /admin/about [post] func (h *AboutHandler) CreateAbout(c *gin.Context) { title := strings.TrimSpace(c.PostForm("title")) if title == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "title is required"}) return } imageSub := strings.TrimSpace(c.PostForm("image_sub")) birthday := strings.TrimSpace(c.PostForm("birthday")) city := strings.TrimSpace(c.PostForm("city")) study := strings.TrimSpace(c.PostForm("study")) website := strings.TrimSpace(c.PostForm("website")) phone := strings.TrimSpace(c.PostForm("phone")) age := strings.TrimSpace(c.PostForm("age")) interests := strings.TrimSpace(c.PostForm("interests")) degree := strings.TrimSpace(c.PostForm("degree")) xValue := strings.TrimSpace(c.PostForm("x")) mail := strings.TrimSpace(c.PostForm("mail")) projectDone := strings.TrimSpace(c.PostForm("project_done")) hapyUser := strings.TrimSpace(c.PostForm("hapy_user")) greatReviews := strings.TrimSpace(c.PostForm("great_reviews")) supportTeam := strings.TrimSpace(c.PostForm("support_team")) done, err := parseOptionalInt(c, "done") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "done must be a number"}) return } userH, err := parseOptionalInt(c, "user_h") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user_h must be a number"}) return } great, err := parseOptionalInt(c, "great") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "great must be a number"}) return } team, err := parseOptionalInt(c, "team") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "team must be a number"}) return } isActivePtr, err := parseOptionalBool(c, "is_active") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "is_active must be true or false"}) return } counterActivePtr, err := parseOptionalBool(c, "counter_active") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "counter_active must be true or false"}) return } isActive := false if isActivePtr != nil { isActive = *isActivePtr } counterActive := false if counterActivePtr != nil { counterActive = *counterActivePtr } imageURL := "" imageFile, imageErr := c.FormFile("image") if imageErr == nil { if imageFile.Size > 5*1024*1024 { c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds 5MB limit"}) return } imageURL, err = utils.SaveOptimizedImage(imageFile, "./uploads/about", "about", &utils.ImageOptions{ Width: config.AppConfig.AboutImageWidth, Height: config.AppConfig.AboutImageHeight, Quality: float32(config.AppConfig.AboutImageQuality), Format: config.AppConfig.AboutImageFormat, Mode: config.AppConfig.AboutImageMode, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + err.Error()}) return } } cvPath := "" cvFile, cvErr := c.FormFile("cv") if cvErr == nil { cvPath, err = saveUploadedFile(c, cvFile, "./uploads/cv", "cv") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save CV: " + err.Error()}) return } } about, err := h.aboutService.CreateAbout( title, imageURL, imageSub, cvPath, birthday, city, study, website, phone, age, interests, degree, xValue, mail, done, projectDone, userH, hapyUser, great, greatReviews, team, supportTeam, isActive, counterActive, ) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, about) } // UpdateAbout godoc // @Summary Update an about entry (Admin) // @Description Update an existing about entry // @Tags admin // @Security ApiKeyAuth // @Accept multipart/form-data // @Produce json // @Param id path string true "About ID" // @Param title formData string false "Title" // @Param image formData file false "Image" // @Param image_sub formData string false "Image subtitle" // @Param cv formData file false "CV file" // @Param birthday formData string false "Birthday" // @Param city formData string false "City" // @Param study formData string false "Study" // @Param website formData string false "Website" // @Param phone formData string false "Phone" // @Param age formData string false "Age" // @Param interests formData string false "Interests" // @Param degree formData string false "Degree" // @Param x formData string false "X" // @Param mail formData string false "Mail" // @Param done formData int false "Done" // @Param project_done formData string false "Project done" // @Param user_h formData int false "User count" // @Param hapy_user formData string false "Happy user" // @Param great formData int false "Great" // @Param great_reviews formData string false "Great reviews" // @Param team formData int false "Team" // @Param support_team formData string false "Support team" // @Param slug formData string false "Slug" // @Param is_active formData bool false "Is active" // @Param counter_active formData bool false "Counter active" // @Success 200 {object} models.About // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /admin/about/{id} [put] func (h *AboutHandler) UpdateAbout(c *gin.Context) { id := c.Param("id") title, hasTitle := getOptionalFormValue(c, "title") imageSub, hasImageSub := getOptionalFormValue(c, "image_sub") birthday, hasBirthday := getOptionalFormValue(c, "birthday") city, hasCity := getOptionalFormValue(c, "city") study, hasStudy := getOptionalFormValue(c, "study") website, hasWebsite := getOptionalFormValue(c, "website") phone, hasPhone := getOptionalFormValue(c, "phone") age, hasAge := getOptionalFormValue(c, "age") interests, hasInterests := getOptionalFormValue(c, "interests") degree, hasDegree := getOptionalFormValue(c, "degree") xValue, hasX := getOptionalFormValue(c, "x") mail, hasMail := getOptionalFormValue(c, "mail") projectDone, hasProjectDone := getOptionalFormValue(c, "project_done") hapyUser, hasHapyUser := getOptionalFormValue(c, "hapy_user") greatReviews, hasGreatReviews := getOptionalFormValue(c, "great_reviews") supportTeam, hasSupportTeam := getOptionalFormValue(c, "support_team") slug, hasSlug := getOptionalFormValue(c, "slug") done, err := parseOptionalInt(c, "done") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "done must be a number"}) return } userH, err := parseOptionalInt(c, "user_h") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "user_h must be a number"}) return } great, err := parseOptionalInt(c, "great") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "great must be a number"}) return } team, err := parseOptionalInt(c, "team") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "team must be a number"}) return } isActivePtr, err := parseOptionalBool(c, "is_active") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "is_active must be true or false"}) return } counterActivePtr, err := parseOptionalBool(c, "counter_active") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "counter_active must be true or false"}) return } var titlePtr *string var imageSubPtr *string var birthdayPtr *string var cityPtr *string var studyPtr *string var websitePtr *string var phonePtr *string var agePtr *string var interestsPtr *string var degreePtr *string var xPtr *string var mailPtr *string var projectDonePtr *string var hapyUserPtr *string var greatReviewsPtr *string var supportTeamPtr *string var slugPtr *string if hasTitle { titlePtr = &title } if hasImageSub { imageSubPtr = &imageSub } if hasBirthday { birthdayPtr = &birthday } if hasCity { cityPtr = &city } if hasStudy { studyPtr = &study } if hasWebsite { websitePtr = &website } if hasPhone { phonePtr = &phone } if hasAge { agePtr = &age } if hasInterests { interestsPtr = &interests } if hasDegree { degreePtr = °ree } if hasX { xPtr = &xValue } if hasMail { mailPtr = &mail } if hasProjectDone { projectDonePtr = &projectDone } if hasHapyUser { hapyUserPtr = &hapyUser } if hasGreatReviews { greatReviewsPtr = &greatReviews } if hasSupportTeam { supportTeamPtr = &supportTeam } if hasSlug { slugPtr = &slug } var imagePtr *string imageFile, imageErr := c.FormFile("image") if imageErr == nil { if imageFile.Size > 5*1024*1024 { c.JSON(http.StatusBadRequest, gin.H{"error": "Image size exceeds 5MB limit"}) return } about, fetchErr := h.aboutService.GetAboutByID(id) if fetchErr != nil { c.JSON(http.StatusNotFound, gin.H{"error": fetchErr.Error()}) return } if about.Image != "" && strings.HasPrefix(about.Image, "/uploads/") { oldPath := "." + about.Image _ = os.Remove(oldPath) } imageURL, saveErr := utils.SaveOptimizedImage(imageFile, "./uploads/about", id, &utils.ImageOptions{ Width: config.AppConfig.AboutImageWidth, Height: config.AppConfig.AboutImageHeight, Quality: float32(config.AppConfig.AboutImageQuality), Format: config.AppConfig.AboutImageFormat, Mode: config.AppConfig.AboutImageMode, }) if saveErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save image: " + saveErr.Error()}) return } imagePtr = &imageURL } var cvPtr *string cvFile, cvErr := c.FormFile("cv") if cvErr == nil { about, fetchErr := h.aboutService.GetAboutByID(id) if fetchErr != nil { c.JSON(http.StatusNotFound, gin.H{"error": fetchErr.Error()}) return } if about.CV != "" && strings.HasPrefix(about.CV, "/uploads/") { oldPath := "." + about.CV _ = os.Remove(oldPath) } cvPath, saveErr := saveUploadedFile(c, cvFile, "./uploads/cv", "cv") if saveErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save CV: " + saveErr.Error()}) return } cvPtr = &cvPath } about, err := h.aboutService.UpdateAbout( id, titlePtr, imagePtr, imageSubPtr, cvPtr, birthdayPtr, cityPtr, studyPtr, websitePtr, phonePtr, agePtr, interestsPtr, degreePtr, xPtr, mailPtr, done, projectDonePtr, userH, hapyUserPtr, great, greatReviewsPtr, team, supportTeamPtr, slugPtr, isActivePtr, counterActivePtr, ) if err != nil { status := http.StatusInternalServerError if err.Error() == "about not found" { status = http.StatusNotFound } c.JSON(status, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, about) } // DeleteAbout godoc // @Summary Delete an about entry (Admin) // @Description Delete an about entry by ID // @Tags admin // @Security ApiKeyAuth // @Param id path string true "About ID" // @Success 200 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /admin/about/{id} [delete] func (h *AboutHandler) DeleteAbout(c *gin.Context) { id := c.Param("id") if err := h.aboutService.DeleteAbout(id); err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "About deleted successfully"}) } func parseOptionalInt(c *gin.Context, key string) (*int, error) { value, exists := c.GetPostForm(key) if !exists { return nil, nil } value = strings.TrimSpace(value) if value == "" { return nil, nil } parsed, err := strconv.Atoi(value) if err != nil { return nil, err } return &parsed, nil } func saveUploadedFile(c *gin.Context, file *multipart.FileHeader, uploadDir string, prefix string) (string, error) { if err := os.MkdirAll(uploadDir, 0755); err != nil { return "", err } ext := filepath.Ext(file.Filename) filename := fmt.Sprintf("%s_%d%s", prefix, time.Now().UnixNano(), ext) fullPath := filepath.Join(uploadDir, filename) if err := c.SaveUploadedFile(file, fullPath); err != nil { return "", err } relPath := filepath.Join(uploadDir, filename) if strings.HasPrefix(relPath, ".") { relPath = relPath[1:] } if !strings.HasPrefix(relPath, "/") { relPath = "/" + relPath } return relPath, nil }