Add initial project structure with core functionality for book and stationery uploads
- 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`.
This commit is contained in:
182
utils/validation.go
Normal file
182
utils/validation.go
Normal file
@@ -0,0 +1,182 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user