feat: added wallet service
This commit is contained in:
49
server/Makefile
Normal file
49
server/Makefile
Normal file
@@ -0,0 +1,49 @@
|
||||
# Go parameters
|
||||
GOCMD=go
|
||||
GOBUILD=$(GOCMD) build
|
||||
GOCLEAN=$(GOCMD) clean
|
||||
GOTEST=$(GOCMD) test
|
||||
GOGET=$(GOCMD) get
|
||||
GOMOD=$(GOCMD) mod
|
||||
GOFMT=gofmt
|
||||
GOLINT=golangci-lint
|
||||
|
||||
# Binary names
|
||||
BINARY_NAME=ccoin
|
||||
BINARY_LINUX=$(BINARY_NAME)-linux-x64
|
||||
BINARY_MAC=$(BINARY_NAME)-macos-arm64
|
||||
BINARY_WINDOWS=$(BINARY_NAME)-windows-x64.exe
|
||||
|
||||
# Build directory
|
||||
BUILD_DIR=build
|
||||
|
||||
# Version info (can be overridden)
|
||||
VERSION?=1.0.0
|
||||
BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
COMMIT?=$(shell git rev-parse HEAD)
|
||||
|
||||
# Linker flags to embed version info
|
||||
LDFLAGS=-ldflags "-X ccoin/config/server.version=$(VERSION) -X ccoin/config/server.buildTime=$(BUILD_TIME) -X ccoin/config/server.commit=$(COMMIT) -s -w" -trimpath
|
||||
|
||||
.PHONY: all build clean test coverage deps fmt lint vet help
|
||||
.PHONY: build-linux build-windows build-darwin run dev
|
||||
.PHONY: docker-build install uninstall
|
||||
|
||||
# Default target
|
||||
all: clean deps fmt vet test build
|
||||
|
||||
# Build the binary
|
||||
build:
|
||||
$(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) -v
|
||||
|
||||
# Build for Linux
|
||||
build-linux:
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_LINUX) -v
|
||||
|
||||
# Build for MacOS
|
||||
build-macos:
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_MAC) -v
|
||||
|
||||
# Build for windows
|
||||
build-windows:
|
||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GOBUILD) $(LDFLAGS) -o $(BUILD_DIR)/$(BINARY_WINDOWS) -v
|
||||
@@ -8,10 +8,15 @@ import (
|
||||
"ccoin/utils"
|
||||
)
|
||||
|
||||
var version = "dev"
|
||||
var buildTime = time.Now().Format(time.RFC3339)
|
||||
var commit = "none"
|
||||
|
||||
type ServerConfig struct {
|
||||
// App details
|
||||
Version string
|
||||
BuildTime string
|
||||
Commit string
|
||||
BaseURL string
|
||||
|
||||
// Server settings
|
||||
@@ -66,9 +71,10 @@ func NewServerConfig() *ServerConfig {
|
||||
// Loads configuration from environment variables with defaults
|
||||
func (c *ServerConfig) loadConfig() {
|
||||
// App details (these would typically come from build-time variables)
|
||||
c.Version = utils.GetEnvOrDefault("VERSION", "dev")
|
||||
c.BuildTime = utils.GetEnvOrDefault("BUILD_TIME", time.Now().Format(time.RFC3339))
|
||||
c.Version = version
|
||||
c.BuildTime = utils.GetEnvOrDefault("BUILD_TIME", buildTime)
|
||||
c.BaseURL = utils.GetEnvOrDefault("BASE_URL", "http://localhost:8080")
|
||||
c.Commit = commit
|
||||
|
||||
// Server settings
|
||||
c.Host = utils.GetEnvOrDefault("SERVER_HOST", "0.0.0.0")
|
||||
@@ -109,6 +115,7 @@ func (c *ServerConfig) logConfig() {
|
||||
c.logger.Info("Server configuration loaded:")
|
||||
c.logger.Info("Host", "value", c.Host)
|
||||
c.logger.Info("Port", "value", c.Port)
|
||||
c.logger.Info("Version", "value", c.Version)
|
||||
c.logger.Info("Development mode", "value", c.DevelopmentMode)
|
||||
c.logger.Info("Mining difficulty", "value", c.MiningDifficulty)
|
||||
c.logger.Info("Mining reward", "value", c.MiningReward)
|
||||
|
||||
@@ -4,6 +4,7 @@ go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/log v0.4.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
package wallet
|
||||
|
||||
import "time"
|
||||
|
||||
type Wallet struct {
|
||||
Address string `db:"address" json:"address"`
|
||||
Label *string `db:"label" json:"label,omitempty"`
|
||||
PasswordHash string `db:"password_hash" json:"password_hash"`
|
||||
Balance float64 `db:"balance" json:"balance"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
LastActivity *time.Time `db:"last_activity" json:"last_activity,omitempty"`
|
||||
}
|
||||
|
||||
type CreateWalletRequest struct {
|
||||
Label *string `json:"label,omitempty"`
|
||||
Password string `json:"password"`
|
||||
|
||||
35
server/routes/wallet/routes.go
Normal file
35
server/routes/wallet/routes.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"ccoin/models/wallet"
|
||||
ws "ccoin/services/wallet"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Sets up routes for wallet operations
|
||||
func WalletRoutes(router *mux.Router, walletService *ws.WalletService) {
|
||||
walletRouter := router.PathPrefix("/wallet").Subrouter()
|
||||
|
||||
// Create a new wallet
|
||||
walletRouter.HandleFunc("/create", func(w http.ResponseWriter, r *http.Request) {
|
||||
var req wallet.CreateWalletRequest
|
||||
|
||||
// Parse the request
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Create the wallet using the service
|
||||
walletResponse, err := walletService.CreateWallet(req)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create wallet: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(walletResponse)
|
||||
}).Methods("POST")
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"ccoin/config/server"
|
||||
"ccoin/config/database"
|
||||
"ccoin/config/server"
|
||||
"ccoin/routes/wallet"
|
||||
ws "ccoin/services/wallet"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize server
|
||||
server.NewServerConfig()
|
||||
dc := database.NewDatabaseConfig()
|
||||
dc.Init()
|
||||
|
||||
// Initialize database
|
||||
dbConfig := database.NewDatabaseConfig()
|
||||
if err := dbConfig.Init(); err != nil {
|
||||
log.Fatal("Database initialization failed:", err)
|
||||
}
|
||||
|
||||
walletService := ws.NewWalletService(dbConfig.GetDB())
|
||||
|
||||
// Set up router
|
||||
r := mux.NewRouter()
|
||||
routes.WalletRoutes(r, walletService)
|
||||
|
||||
// Start server
|
||||
log.Info("Starting server on :8080")
|
||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||
log.Fatal("Failed to start server:", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
219
server/services/wallet/service.go
Normal file
219
server/services/wallet/service.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"ccoin/utils/crypto"
|
||||
"database/sql"
|
||||
"ccoin/models/wallet"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type WalletService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// Creates a new instance of WalletService
|
||||
func NewWalletService(db *sql.DB) *WalletService {
|
||||
return &WalletService{db: db}
|
||||
}
|
||||
|
||||
// Creates a new wallet with optional label
|
||||
func (ws *WalletService) CreateWallet(req wallet.CreateWalletRequest) (wallet.WalletResponse, error) {
|
||||
address := crypto.GenerateWalletAddress()
|
||||
timestamp := time.Now()
|
||||
var passwordHash string
|
||||
if req.Password != "" {
|
||||
passwordHash = crypto.HashPassword(req.Password)
|
||||
} else {
|
||||
return wallet.WalletResponse{}, fmt.Errorf("password field is required")
|
||||
}
|
||||
|
||||
_, err := ws.db.Exec(`
|
||||
INSERT INTO wallets (address, balance, label, password_hash, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
address, 0.0, req.Label, passwordHash, timestamp.Unix(),
|
||||
)
|
||||
if err != nil {
|
||||
return wallet.WalletResponse{}, err
|
||||
}
|
||||
|
||||
return wallet.WalletResponse{
|
||||
Address: address,
|
||||
Balance: 0.0,
|
||||
Label: req.Label,
|
||||
PasswordHash: passwordHash,
|
||||
CreatedAt: timestamp.Unix(),
|
||||
LastActivity: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Gets wallet by address
|
||||
func (ws *WalletService) GetWallet(address string) (*wallet.WalletResponse, error) {
|
||||
var w wallet.Wallet
|
||||
row := ws.db.QueryRow(`
|
||||
SELECT address, balance, label, password_hash, created_at, last_activity
|
||||
FROM wallets WHERE address = $1`, address)
|
||||
|
||||
err := row.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash, &w.CreatedAt, &w.LastActivity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wallet.WalletResponse{
|
||||
Address: w.Address,
|
||||
Balance: w.Balance,
|
||||
Label: w.Label,
|
||||
PasswordHash: w.PasswordHash,
|
||||
CreatedAt: w.CreatedAt.Unix(),
|
||||
LastActivity: ptrTime(w.LastActivity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Gets wallet balance
|
||||
func (ws *WalletService) GetWalletBalance(address string) (wallet.BalanceResponse, error) {
|
||||
var balance float64
|
||||
err := ws.db.QueryRow(`SELECT balance FROM wallets WHERE address = $1`, address).Scan(&balance)
|
||||
if err != nil {
|
||||
return wallet.BalanceResponse{}, errors.New("wallet not found")
|
||||
}
|
||||
return wallet.BalanceResponse{
|
||||
Address: address,
|
||||
Balance: balance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Updates wallet balance
|
||||
func (ws *WalletService) UpdateBalance(address string, amount float64) (bool, error) {
|
||||
_, err := ws.db.Exec(`UPDATE wallets SET balance = balance + $1, last_activity = $2 WHERE address = $3`, amount, time.Now().Unix(), address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Sets wallet balance to specific amount
|
||||
func (ws *WalletService) SetBalance(address string, amount float64) (bool, error) {
|
||||
_, err := ws.db.Exec(`UPDATE wallets SET balance = $1, last_activity = $2 WHERE address = $3`, amount, time.Now().Unix(), address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Updates wallet label
|
||||
func (ws *WalletService) UpdateLabel(address string, req wallet.UpdateWalletRequest) (bool, error) {
|
||||
result, err := ws.db.Exec(`UPDATE wallets SET label = $1 WHERE address = $2`, address, req.Label)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rowsAffected, _ := result.RowsAffected()
|
||||
return rowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// Checks if wallet exists
|
||||
func (ws *WalletService) WalletExists(address string) (bool, error) {
|
||||
var count int64
|
||||
err := ws.db.QueryRow(`SELECT COUNT(*) FROM wallets WHERE address = $1`, address).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// Gets all wallets with pagination
|
||||
func (ws *WalletService) GetAllWallets(limit, offset int) ([]wallet.WalletResponse, error) {
|
||||
query := fmt.Sprintf(`
|
||||
SELECT address, balance, label, password_hash, created_at, last_activity
|
||||
FROM wallets ORDER BY created_at DESC LIMIT %d OFFSET %d`, limit, offset)
|
||||
|
||||
rows, err := ws.db.Query(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wallets []wallet.WalletResponse
|
||||
for rows.Next() {
|
||||
var w wallet.Wallet
|
||||
if err := rows.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash,
|
||||
&w.CreatedAt, &w.LastActivity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wallets = append(wallets, wallet.WalletResponse{
|
||||
Address: w.Address,
|
||||
Balance: w.Balance,
|
||||
Label: w.Label,
|
||||
PasswordHash: w.PasswordHash,
|
||||
CreatedAt: w.CreatedAt.Unix(),
|
||||
LastActivity: ptrTime(w.LastActivity),
|
||||
})
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
// Gets total number of wallets
|
||||
func (ws *WalletService) GetTotalWalletCount() (int64, error) {
|
||||
var count int64
|
||||
err := ws.db.QueryRow(`SELECT COUNT(*) FROM wallets`).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// Gets wallet with balance greater than specified amount
|
||||
func (ws *WalletService) GetWalletsWithBalance(minBalance float64) ([]wallet.WalletResponse, error) {
|
||||
rows, err := ws.db.Query(`SELECT address, balance, label, password_hash, created_at, last_activity FROM wallets WHERE balance > $1 ORDER BY balance DESC`, minBalance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var wallets []wallet.WalletResponse
|
||||
for rows.Next() {
|
||||
var w wallet.Wallet
|
||||
if err := rows.Scan(&w.Address, &w.Balance, &w.Label, &w.PasswordHash, &w.CreatedAt, &w.LastActivity); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wallets = append(wallets, wallet.WalletResponse{
|
||||
Address: w.Address,
|
||||
Balance: w.Balance,
|
||||
Label: w.Label,
|
||||
PasswordHash: w.PasswordHash,
|
||||
CreatedAt: w.CreatedAt.Unix(),
|
||||
LastActivity: ptrTime(w.LastActivity),
|
||||
})
|
||||
}
|
||||
return wallets, nil
|
||||
}
|
||||
|
||||
// Updates last activity timestamp
|
||||
func (ws *WalletService) UpdateLastActivity(address string) (bool, error) {
|
||||
_, err := ws.db.Exec(`UPDATE wallets SET last_activity = $1 WHERE address = $2`, time.Now().Unix(), address)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Gets total supply across all wallets
|
||||
func (ws *WalletService) GetTotalSupply() (float64, error) {
|
||||
var totalSupply float64
|
||||
err := ws.db.QueryRow(`SELECT SUM(balance) FROM wallets`).Scan(&totalSupply)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return totalSupply, nil
|
||||
}
|
||||
|
||||
// Helper function to convert *time.Time to *int64 for JSON response
|
||||
func ptrTime(t *time.Time) *int64 {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
unixTime := t.Unix()
|
||||
return &unixTime
|
||||
}
|
||||
@@ -1,2 +1,131 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"ccoin/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var words = []string{
|
||||
"phoenix", "dragon", "tiger", "eagle", "wolf", "lion", "bear", "shark",
|
||||
"falcon", "raven", "cobra", "viper", "panther", "jaguar", "leopard", "cheetah",
|
||||
"thunder", "lightning", "storm", "blizzard", "tornado", "hurricane", "cyclone", "tempest",
|
||||
"crystal", "diamond", "emerald", "ruby", "sapphire", "topaz", "amethyst", "opal",
|
||||
"shadow", "ghost", "phantom", "spirit", "wraith", "specter", "demon", "angel",
|
||||
"fire", "ice", "earth", "wind", "water", "metal", "wood", "void",
|
||||
"star", "moon", "sun", "comet", "meteor", "galaxy", "nebula", "cosmos",
|
||||
"blade", "sword", "arrow", "spear", "shield", "armor", "crown", "throne",
|
||||
"mountain", "ocean", "forest", "desert", "valley", "river", "lake", "cave",
|
||||
"knight", "warrior", "mage", "archer", "rogue", "paladin", "wizard", "sage",
|
||||
}
|
||||
|
||||
// Generates a wallet address in format: random_word:random_6_digits
|
||||
func GenerateWalletAddress() string {
|
||||
randomWord := words[utils.RandInt(0, len(words))]
|
||||
randomDigits := fmt.Sprintf("%06d", utils.RandInt(0, 1000000))
|
||||
return fmt.Sprintf("%s:%s", randomWord, randomDigits)
|
||||
}
|
||||
|
||||
// Validates if an address follows the correct format
|
||||
func IsValidAddress(address string) bool {
|
||||
parts := strings.Split(address, ":")
|
||||
if len(parts) != 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
word := parts[0]
|
||||
digits := parts[1]
|
||||
|
||||
return len(word) > 0 &&
|
||||
utils.IsAlpha(word) &&
|
||||
len(digits) == 6 &&
|
||||
utils.IsDigit(digits)
|
||||
}
|
||||
|
||||
// Generates SHA-256 hash of input string
|
||||
func SHA256(input string) string {
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(input))
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))
|
||||
}
|
||||
|
||||
// Hashes password
|
||||
func HashPassword(password string) string {
|
||||
return SHA256(fmt.Sprintf("ccoin_password_%s", password))
|
||||
}
|
||||
|
||||
// Generates a transaction hash
|
||||
func GenerateTransactionHash(fromAddress *string, toAddress string, amount float64, timestamp int64, nonce int64) string {
|
||||
from := "genesis"
|
||||
if fromAddress != nil {
|
||||
from = *fromAddress
|
||||
}
|
||||
input := fmt.Sprintf("%s:%s:%f:%d:%d", from, toAddress, amount, timestamp, nonce)
|
||||
return SHA256(input)
|
||||
}
|
||||
|
||||
// Generates a block hash
|
||||
func GenerateBlockHash(previousHash string, merkleRoot string, timestamp int64, difficulty int, nonce int64) string {
|
||||
input := fmt.Sprintf("%s:%s:%d:%d:%d", previousHash, merkleRoot, timestamp, difficulty, nonce)
|
||||
return SHA256(input)
|
||||
}
|
||||
|
||||
// Validates if a hash meets the mining difficulty requirement
|
||||
func IsValidHash(hash string, difficulty int) bool {
|
||||
target := strings.Repeat("0", difficulty)
|
||||
return strings.HasPrefix(hash, target)
|
||||
}
|
||||
|
||||
// Generates mining job id
|
||||
func GenerateJobId() string {
|
||||
return SHA256(fmt.Sprintf("job:%d:%d", utils.GetCurrentTimeMillis(), utils.RandInt(0, 1<<63-1)))[:16]
|
||||
}
|
||||
|
||||
// Calculate merkle root from transaction hashes
|
||||
func CalculateMerkleRoot(transactionHashes []string) string {
|
||||
if len(transactionHashes) == 0 {
|
||||
return SHA256("empty")
|
||||
}
|
||||
|
||||
if len(transactionHashes) == 1 {
|
||||
return transactionHashes[0]
|
||||
}
|
||||
|
||||
hashes := transactionHashes
|
||||
|
||||
for len(hashes) > 1 {
|
||||
var newHashes []string
|
||||
for i := 0; i < len(hashes); i += 2 {
|
||||
left := hashes[i]
|
||||
right := left // Default to left if there's no right
|
||||
if i+1< len(hashes) {
|
||||
right = hashes[i+1]
|
||||
}
|
||||
newHashes = append(newHashes, SHA256(fmt.Sprintf("%s:%s", left, right)))
|
||||
}
|
||||
hashes = newHashes
|
||||
}
|
||||
|
||||
return hashes[0]
|
||||
}
|
||||
|
||||
// Generates a random nonce for mining
|
||||
func GenerateNonce() int64 {
|
||||
return int64(utils.RandInt(0, 1<<63-1))
|
||||
}
|
||||
|
||||
// Validates transaction hash format
|
||||
func IsValidTransactionHash(hash string) bool {
|
||||
return len(hash) == 64 && utils.IsHex(hash)
|
||||
}
|
||||
|
||||
// Validates block hash format
|
||||
func IsValidBlockHash(hash string) bool {
|
||||
return len(hash) == 64 && utils.IsHex(hash)
|
||||
}
|
||||
|
||||
// Verifies password hash
|
||||
func VerifyPassword(password string, storedHash string) bool {
|
||||
return HashPassword(password) == storedHash
|
||||
}
|
||||
|
||||
48
server/utils/general.go
Normal file
48
server/utils/general.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generates a random integer in the range (min, max)
|
||||
func RandInt(min, max int) int {
|
||||
n, _ := rand.Int(rand.Reader, big.NewInt(int64(max-min)))
|
||||
return int(n.Int64()) + min
|
||||
}
|
||||
|
||||
// Get the current time in milliseconds
|
||||
func GetCurrentTimeMillis() int64 {
|
||||
return time.Now().UnixNano() / int64(time.Millisecond) // Convert nanoseconds to milliseconds
|
||||
}
|
||||
|
||||
// IsAlpha checks if the string contains only alphabetic characters
|
||||
func IsAlpha(s string) bool {
|
||||
for _, r := range s {
|
||||
if !('a' <= r && r <= 'z') && !('A' <= r && r <= 'Z') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsDigit checks if the string contains only digits
|
||||
func IsDigit(s string) bool {
|
||||
for _, r := range s {
|
||||
if !(r >= '0' && r <= '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IsHex checks if the string is a valid hexadecimal representation
|
||||
func IsHex(s string) bool {
|
||||
for _, r := range s {
|
||||
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user