This commit is contained in:
+16
-14
@@ -36,20 +36,6 @@ func (s *Server) Ping(context.Context, *proto.PingReq) (*proto.PingRsp, error) {
|
||||
return &proto.PingRsp{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddUser(ctx context.Context, req *proto.AddUserReq) (*proto.User, error) {
|
||||
user, err := s.userService.AddUser(
|
||||
ctx,
|
||||
&user.UserEntity{
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mapUser(user), nil
|
||||
}
|
||||
|
||||
func (s *Server) Login(ctx context.Context, req *proto.LoginReq) (*proto.User, error) {
|
||||
user, err := s.userService.Login(
|
||||
ctx,
|
||||
@@ -148,6 +134,22 @@ func (s *Server) AddWaste(ctx context.Context, req *proto.AddWasteReq) (*proto.W
|
||||
Price: int(req.Price),
|
||||
Amount: req.Amount,
|
||||
CategoryId: int(req.CategoryId),
|
||||
BudgetId: int(req.BudgetId),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mapWaste(waste), nil
|
||||
}
|
||||
|
||||
func (s *Server) AddWasteByText(ctx context.Context, req *proto.AddWasteTextReq) (*proto.Waste, error) {
|
||||
waste, err := s.wasteService.AddWasteByText(
|
||||
ctx,
|
||||
&waste.WasteTextEntity{
|
||||
Text: req.Text,
|
||||
CategoryId: int(req.CategoryId),
|
||||
BudgetId: int(req.BudgetId),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user