- Created main application entry point in `main.go`. - Added configuration management in `config/config.go` and tests in `config/config_test.go`. - Implemented handlers for book and stationery uploads in `handlers/book.go` and `handlers/stationery.go`, including validation logic. - Established database connection and services in `services/database.go` and `services/book_service.go`. - Defined models for books and stationery in `models/book.go` and `models/stationery.go`. - Set up Firebase integration for image uploads in `services/firebase.go`. - Created migration scripts for database schema in `migrations/001_create_tables.sql` and subsequent updates. - Added CORS and error handling middleware. - Included comprehensive tests for handlers, services, and utilities. - Documented API endpoints and usage in `README.md` and migration instructions in `migrations/README.md`. - Introduced `.gitignore` to exclude unnecessary files and directories. - Added Go module support with `go.mod` and `go.sum` files. - Implemented utility functions for slug generation and validation in `utils/slug.go` and `utils/validation.go`.
183 lines
5.5 KiB
Go
183 lines
5.5 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ValidationError represents a validation error with field name
|
|
type ValidationError struct {
|
|
Field string
|
|
Message string
|
|
}
|
|
|
|
func (e *ValidationError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Field, e.Message)
|
|
}
|
|
|
|
// ValidateBookFields validates all book form fields
|
|
func ValidateBookFields(bookName, cost, price, discount, quantity, publisherAuthor, category, description string) []*ValidationError {
|
|
var errors []*ValidationError
|
|
|
|
// Book Name
|
|
bookName = strings.TrimSpace(bookName)
|
|
if bookName == "" {
|
|
errors = append(errors, &ValidationError{Field: "book_name", Message: "is required"})
|
|
} else if len(bookName) > 200 {
|
|
errors = append(errors, &ValidationError{Field: "book_name", Message: "exceeds 200 characters"})
|
|
}
|
|
|
|
// Cost
|
|
if cost == "" {
|
|
errors = append(errors, &ValidationError{Field: "cost", Message: "is required"})
|
|
} else {
|
|
costVal, err := strconv.ParseFloat(cost, 64)
|
|
if err != nil || costVal <= 0 {
|
|
errors = append(errors, &ValidationError{Field: "cost", Message: "must be a positive number"})
|
|
}
|
|
}
|
|
|
|
// Price
|
|
if price == "" {
|
|
errors = append(errors, &ValidationError{Field: "price", Message: "is required"})
|
|
} else {
|
|
priceVal, err := strconv.ParseFloat(price, 64)
|
|
if err != nil || priceVal <= 0 {
|
|
errors = append(errors, &ValidationError{Field: "price", Message: "must be a positive number"})
|
|
}
|
|
}
|
|
|
|
// Discount (optional)
|
|
if discount != "" {
|
|
discountVal, err := strconv.ParseFloat(discount, 64)
|
|
if err != nil || discountVal < 0 {
|
|
errors = append(errors, &ValidationError{Field: "discount", Message: "must be a non-negative number"})
|
|
}
|
|
}
|
|
|
|
// Quantity
|
|
if quantity == "" {
|
|
errors = append(errors, &ValidationError{Field: "quantity", Message: "is required"})
|
|
} else {
|
|
quantityVal, err := strconv.Atoi(quantity)
|
|
if err != nil || quantityVal <= 0 {
|
|
errors = append(errors, &ValidationError{Field: "quantity", Message: "must be a positive integer"})
|
|
}
|
|
}
|
|
|
|
// Publisher/Author
|
|
publisherAuthor = strings.TrimSpace(publisherAuthor)
|
|
if publisherAuthor == "" {
|
|
errors = append(errors, &ValidationError{Field: "publisher_author", Message: "is required"})
|
|
} else if len(publisherAuthor) > 200 {
|
|
errors = append(errors, &ValidationError{Field: "publisher_author", Message: "exceeds 200 characters"})
|
|
}
|
|
|
|
// Category
|
|
category = strings.TrimSpace(category)
|
|
if category == "" {
|
|
errors = append(errors, &ValidationError{Field: "category", Message: "is required"})
|
|
} else if len(category) > 100 {
|
|
errors = append(errors, &ValidationError{Field: "category", Message: "exceeds 100 characters"})
|
|
}
|
|
|
|
// Description (optional)
|
|
if description != "" && len(description) > 1000 {
|
|
errors = append(errors, &ValidationError{Field: "description", Message: "exceeds 1000 characters"})
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// ValidateStationeryFields validates all stationery form fields
|
|
func ValidateStationeryFields(stationeryName, cost, price, discount, quantity, category, description string) []*ValidationError {
|
|
var errors []*ValidationError
|
|
|
|
// Stationery Name
|
|
stationeryName = strings.TrimSpace(stationeryName)
|
|
if stationeryName == "" {
|
|
errors = append(errors, &ValidationError{Field: "stationery_name", Message: "is required"})
|
|
} else if len(stationeryName) > 200 {
|
|
errors = append(errors, &ValidationError{Field: "stationery_name", Message: "exceeds 200 characters"})
|
|
}
|
|
|
|
// Cost
|
|
if cost == "" {
|
|
errors = append(errors, &ValidationError{Field: "cost", Message: "is required"})
|
|
} else {
|
|
costVal, err := strconv.ParseFloat(cost, 64)
|
|
if err != nil || costVal <= 0 {
|
|
errors = append(errors, &ValidationError{Field: "cost", Message: "must be a positive number"})
|
|
}
|
|
}
|
|
|
|
// Price
|
|
if price == "" {
|
|
errors = append(errors, &ValidationError{Field: "price", Message: "is required"})
|
|
} else {
|
|
priceVal, err := strconv.ParseFloat(price, 64)
|
|
if err != nil || priceVal <= 0 {
|
|
errors = append(errors, &ValidationError{Field: "price", Message: "must be a positive number"})
|
|
}
|
|
}
|
|
|
|
// Discount (optional)
|
|
if discount != "" {
|
|
discountVal, err := strconv.ParseFloat(discount, 64)
|
|
if err != nil || discountVal < 0 {
|
|
errors = append(errors, &ValidationError{Field: "discount", Message: "must be a non-negative number"})
|
|
}
|
|
}
|
|
|
|
// Quantity
|
|
if quantity == "" {
|
|
errors = append(errors, &ValidationError{Field: "quantity", Message: "is required"})
|
|
} else {
|
|
quantityVal, err := strconv.Atoi(quantity)
|
|
if err != nil || quantityVal <= 0 {
|
|
errors = append(errors, &ValidationError{Field: "quantity", Message: "must be a positive integer"})
|
|
}
|
|
}
|
|
|
|
// Category
|
|
category = strings.TrimSpace(category)
|
|
if category == "" {
|
|
errors = append(errors, &ValidationError{Field: "category", Message: "is required"})
|
|
} else if len(category) > 100 {
|
|
errors = append(errors, &ValidationError{Field: "category", Message: "exceeds 100 characters"})
|
|
}
|
|
|
|
// Description (optional)
|
|
if description != "" && len(description) > 1000 {
|
|
errors = append(errors, &ValidationError{Field: "description", Message: "exceeds 1000 characters"})
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// ValidateFileType checks if file type is allowed
|
|
func ValidateFileType(contentType string) error {
|
|
allowedTypes := map[string]bool{
|
|
"image/png": true,
|
|
"image/jpeg": true,
|
|
"image/jpg": true,
|
|
"image/webp": true,
|
|
}
|
|
|
|
if !allowedTypes[contentType] {
|
|
return fmt.Errorf("invalid file type. Only PNG, JPEG, and WEBP are allowed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateFileSize checks if file size is within limit
|
|
func ValidateFileSize(size, maxSize int64) error {
|
|
if size > maxSize {
|
|
return fmt.Errorf("file size exceeds %dMB limit", maxSize/(1024*1024))
|
|
}
|
|
|
|
return nil
|
|
}
|