bank-emul/internal/storage/storage.go
2026-03-07 19:45:24 +03:00

168 lines
3.3 KiB
Go

package storage
import (
"encoding/json"
"fmt"
"math/rand"
"os"
"sync"
)
type Card struct {
Number string `json:"number"`
HolderName string `json:"holder_name"`
Expiry string `json:"expiry"`
CVV string `json:"cvv"`
Balance float64 `json:"balance"`
}
type Account struct {
ID int `json:"id"`
Name string `json:"name"`
Cards []*Card `json:"cards"`
}
type Store struct {
mu sync.RWMutex
Accounts []*Account `json:"accounts"`
filePath string
}
func New(path string) *Store {
s := &Store{filePath: path}
data, err := os.ReadFile(path)
if err == nil {
json.Unmarshal(data, &s.Accounts)
}
return s
}
func (s *Store) Save() error {
s.mu.Lock()
defer s.mu.Unlock()
data, _ := json.MarshalIndent(s.Accounts, "", " ")
return os.WriteFile(s.filePath, data, 0644)
}
func (s *Store) AddAccount(name string) *Account {
s.mu.Lock()
defer s.mu.Unlock()
acc := &Account{ID: len(s.Accounts) + 1, Name: name}
s.Accounts = append(s.Accounts, acc)
return acc
}
func (s *Store) reload() {
data, err := os.ReadFile(s.filePath)
if err != nil {
return
}
var accounts []*Account
if json.Unmarshal(data, &accounts) == nil {
s.Accounts = accounts
}
}
func (s *Store) FindCardByNumber(number string) (*Account, *Card) {
s.mu.Lock()
s.reload()
s.mu.Unlock()
s.mu.RLock()
defer s.mu.RUnlock()
for _, acc := range s.Accounts {
for _, c := range acc.Cards {
if c.Number == number {
return acc, c
}
}
}
return nil, nil
}
func (s *Store) Charge(cardNumber string, amount float64) error {
s.mu.Lock()
defer s.mu.Unlock()
s.reload()
for _, acc := range s.Accounts {
for _, c := range acc.Cards {
if c.Number == cardNumber {
if c.Balance < amount {
return fmt.Errorf("insufficient funds: %.2f < %.2f", c.Balance, amount)
}
c.Balance -= amount
return nil
}
}
}
return fmt.Errorf("card not found")
}
func (s *Store) AddCard(accountID int) (*Card, error) {
s.mu.Lock()
defer s.mu.Unlock()
for _, acc := range s.Accounts {
if acc.ID == accountID {
card := &Card{
Number: generateCardNumber(),
HolderName: acc.Name,
Expiry: fmt.Sprintf("%02d/%02d", rand.Intn(12)+1, 26+rand.Intn(5)),
CVV: fmt.Sprintf("%03d", rand.Intn(1000)),
Balance: 0,
}
acc.Cards = append(acc.Cards, card)
return card, nil
}
}
return nil, fmt.Errorf("account %d not found", accountID)
}
func (s *Store) TopUp(cardNumber string, amount float64) error {
s.mu.Lock()
defer s.mu.Unlock()
for _, acc := range s.Accounts {
for _, c := range acc.Cards {
if c.Number == cardNumber {
c.Balance += amount
return nil
}
}
}
return fmt.Errorf("card not found")
}
func (s *Store) ListAccounts() []*Account {
s.mu.RLock()
defer s.mu.RUnlock()
return s.Accounts
}
func generateCardNumber() string {
prefix := "4228"
body := make([]byte, 11)
for i := range body {
body[i] = byte(rand.Intn(10))
}
partial := prefix
for _, b := range body {
partial += fmt.Sprintf("%d", b)
}
digits := make([]int, 15)
for i, ch := range partial {
digits[i] = int(ch - '0')
}
sum := 0
for i := 0; i < 15; i++ {
d := digits[14-i]
if i%2 == 0 {
d *= 2
if d > 9 {
d -= 9
}
}
sum += d
}
check := (10 - (sum % 10)) % 10
return partial + fmt.Sprintf("%d", check)
}