add text waste route
continuous-integration/drone/push Build is failing

This commit is contained in:
2024-11-28 06:43:05 +07:00
parent 26ca649e8e
commit 0ed38b7cc0
15 changed files with 744 additions and 430 deletions
@@ -60,23 +60,6 @@ func (s *BudgetService) AddBudget(ctx context.Context, budget *BudgetEntity) (*B
return nil, err
}
defaultCategories := []*category.CategoryEntity{
{
Name: "Транспорт",
BudgetId: budget.Id,
},
{
Name: "Продукты",
BudgetId: budget.Id,
},
}
for _, category := range defaultCategories {
_, err = s.categoryService.AddCategory(ctx, category)
if err != nil {
return nil, err
}
}
if err = tx.Commit(ctx); err != nil {
return nil, err
}
@@ -42,10 +42,10 @@ func (s *CategoryService) AddCategory(ctx context.Context, category *CategoryEnt
return category, nil
}
func (s *CategoryService) GetCategory(ctx context.Context, categoryId int) (*CategoryEntity, error) {
query := `SELECT id, name, budget_id, favorite, monthly_limit FROM categories WHERE id = @id`
func (s *CategoryService) GetCategory(ctx context.Context, budgetId int, name string) (*CategoryEntity, error) {
query := `SELECT id, name, budget_id, favorite, monthly_limit FROM categories WHERE LOWER(name) = LOWER(@name)`
args := pgx.NamedArgs{
"id": categoryId,
"name": name,
}
rows, err := s.db.Query(ctx, query, args)
if err != nil {
@@ -61,5 +61,8 @@ func (s *CategoryService) GetCategory(ctx context.Context, categoryId int) (*Cat
}
categories = append(categories, category)
}
return categories[0], nil
if len(categories) > 0 {
return categories[0], nil
}
return nil, nil
}
File diff suppressed because one or more lines are too long
+7 -23
View File
@@ -4,11 +4,8 @@ import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -42,33 +39,20 @@ func NewUserService(
}
}
func (s *UserService) AddUser(ctx context.Context, user *UserEntity) (*UserEntity, error) {
query := `INSERT INTO users (username, password) VALUES (@username, @password) RETURNING id`
args := pgx.NamedArgs{
"username": user.Username,
"password": hashPassword(user.Username, user.Password, "crab"),
}
if err := s.db.QueryRow(ctx, query, args).Scan(&user.Id); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) {
if pgErr.Code == "23505" && pgErr.ConstraintName == "users_username_key" { // unique_violation
return nil, &UsernameAlreadyExistsErr{}
}
}
return nil, fmt.Errorf("unable to insert row: %w", err)
}
return user, nil
}
func (s *UserService) Login(ctx context.Context, user *UserEntity) (*UserEntity, error) {
query := `SELECT id FROM users WHERE username = @username AND password = @password`
args := pgx.NamedArgs{
"username": user.Username,
"password": hashPassword(user.Username, user.Password, "crab"),
}
query := `SELECT id FROM users WHERE username = @username AND password = @password LIMIT 1`
if err := s.db.QueryRow(ctx, query, args).Scan(&user.Id); err != nil {
return nil, &UserNotFoundErr{}
query := `INSERT INTO users (username, password) VALUES (@username, @password) RETURNING id`
if err := s.db.QueryRow(ctx, query, args).Scan(&user.Id); err != nil {
return user, err
}
}
return user, nil
}
+147 -11
View File
@@ -3,19 +3,30 @@ package waste
import (
"context"
"fmt"
"strconv"
"strings"
"git.3crabs.ru/save_my_money/smm_core/internal/services/category"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
type WasteEntity struct {
Id int
Name string
Price int
Amount float32
BudgetId int
Id int
Name string
Price int
Amount float32
CategoryId int
CategoryName string
BudgetId int
}
type WasteTextEntity struct {
Text string
CategoryId int
BudgetId int
}
type WasteService struct {
@@ -34,17 +45,12 @@ func NewWasteService(
}
func (s *WasteService) AddWaste(ctx context.Context, waste *WasteEntity) (*WasteEntity, error) {
category, err := s.categoryService.GetCategory(ctx, waste.CategoryId)
if err != nil {
return nil, err
}
query := `INSERT INTO wastes (name, price, amount, budget_id, category_id) VALUES (@name, @price, @amount, @budget_id, @category_id) RETURNING id`
args := pgx.NamedArgs{
"name": waste.Name,
"price": waste.Price,
"amount": waste.Amount,
"budget_id": category.BudgetId,
"budget_id": waste.BudgetId,
"category_id": waste.CategoryId,
}
if err := s.db.QueryRow(ctx, query, args).Scan(&waste.Id); err != nil {
@@ -52,3 +58,133 @@ func (s *WasteService) AddWaste(ctx context.Context, waste *WasteEntity) (*Waste
}
return waste, nil
}
func (s *WasteService) GetWaste(ctx context.Context, budgetId int, name string) (*WasteEntity, error) {
query := `SELECT id, name, price, amount, budget_id, category_id FROM wastes WHERE budget_id = @budget_id AND LOWER(name) = LOWER(@name) LIMIT 1`
args := pgx.NamedArgs{
"name": name,
"budget_id": budgetId,
}
rows, err := s.db.Query(ctx, query, args)
if err != nil {
return nil, err
}
wastes := []*WasteEntity{}
defer rows.Close()
for rows.Next() {
waste := &WasteEntity{}
err = rows.Scan(&waste.Id, &waste.Name, &waste.Price, &waste.Amount, &waste.BudgetId, &waste.CategoryId)
if err != nil {
return nil, err
}
wastes = append(wastes, waste)
}
if len(wastes) > 0 {
return wastes[0], nil
}
return nil, nil
}
func (s *WasteService) AddWasteByText(ctx context.Context, wasteText *WasteTextEntity) (*WasteEntity, error) {
waste := s.parseWaste(wasteText.Text)
var categoryId *int
if waste.CategoryName != "" {
c, err := s.categoryService.GetCategory(ctx, wasteText.BudgetId, waste.CategoryName)
if err != nil {
return nil, err
}
if c == nil {
c, err = s.categoryService.AddCategory(ctx, &category.CategoryEntity{
Name: waste.CategoryName,
BudgetId: int(wasteText.BudgetId),
})
if err != nil {
return nil, err
}
}
categoryId = &c.Id
}
if categoryId == nil {
w, err := s.GetWaste(ctx, wasteText.BudgetId, waste.Name)
if err != nil {
return nil, err
}
if w != nil {
categoryId = &w.CategoryId
}
}
if categoryId == nil {
return nil, fmt.Errorf("bad request")
}
query := `INSERT INTO wastes (name, price, amount, budget_id, category_id) VALUES (@name, @price, @amount, @budget_id, @category_id) RETURNING id`
args := pgx.NamedArgs{
"name": waste.Name,
"price": waste.Price,
"amount": waste.Amount,
"budget_id": wasteText.BudgetId,
"category_id": *categoryId,
}
if err := s.db.QueryRow(ctx, query, args).Scan(&waste.Id); err != nil {
return nil, fmt.Errorf("unable to insert row: %w", err)
}
return waste, nil
}
func (s *WasteService) parseWaste(text string) *WasteEntity {
text = strings.TrimSpace(text)
arr := strings.Split(text, ":")
waste := &WasteEntity{}
switch len(arr) {
case 1:
arr = strings.Split(strings.TrimSpace(arr[0]), " ")
case 2:
waste.CategoryName = strToName(arr[0])
arr = strings.Split(strings.TrimSpace(arr[1]), " ")
}
switch len(arr) {
case 2:
waste.Name = strToName(arr[0])
waste.Amount = 1
waste.Price = strToPrice(arr[1])
case 3:
waste.Name = strToName(arr[0])
waste.Amount = strToAmount(arr[1])
waste.Price = strToPrice(arr[2])
}
return waste
}
func strToFloat(s string) float32 {
a, err := strconv.ParseFloat(s, 32)
if err != nil {
panic(err)
}
return float32(a)
}
func strToPrice(s string) int {
s = strings.TrimSpace(s)
s = strings.TrimSuffix(s, "/кг")
s = strings.TrimSuffix(s, "/шт")
s = strings.TrimSuffix(s, "р")
s = strings.TrimSuffix(s, "руб")
s = strings.TrimSuffix(s, "рублей")
return int(strToFloat(s) * 100)
}
func strToAmount(s string) float32 {
s = strings.TrimSpace(s)
s = strings.TrimSuffix(s, "шт")
s = strings.TrimSuffix(s, "кг")
return strToFloat(s)
}
func strToName(s string) string {
caser := cases.Title(language.Russian)
return caser.String(strings.TrimSpace(strings.ToLower(s)))
}
@@ -0,0 +1,83 @@
package waste
import (
"reflect"
"testing"
)
func TestWasteService_parseWaste(t *testing.T) {
type args struct {
text string
}
tests := []struct {
name string
args args
want *WasteEntity
}{
{
name: "full",
args: args{
text: "Транспорт: такси 2 200",
},
want: &WasteEntity{
CategoryName: "транспорт",
Name: "такси",
Amount: 2,
Price: 20000,
},
},
{
name: "full without amount",
args: args{
text: "Транспорт: такси 200",
},
want: &WasteEntity{
CategoryName: "транспорт",
Name: "такси",
Amount: 1,
Price: 20000,
},
},
{
name: "without category",
args: args{
text: "такси 2 200",
},
want: &WasteEntity{
Name: "такси",
Amount: 2,
Price: 20000,
},
},
{
name: "without amount",
args: args{
text: "такси 200",
},
want: &WasteEntity{
Name: "такси",
Amount: 1,
Price: 20000,
},
},
{
name: "р",
args: args{
text: "такси 200р",
},
want: &WasteEntity{
Name: "такси",
Amount: 1,
Price: 20000,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &WasteService{}
if got := s.parseWaste(tt.args.text); !reflect.DeepEqual(got, tt.want) {
t.Errorf("WasteService.parseWaste() = %v, want %v", got, tt.want)
}
})
}
}