generated from yandex-praktikum/go-musthave-diploma-tpl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Пешков Дмитрий
committed
Sep 5, 2024
1 parent
5a440dc
commit b72e9cb
Showing
22 changed files
with
1,284 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,16 @@ | ||
package main | ||
|
||
func main() {} | ||
import ( | ||
"github.com/spqrcor/gofermart/internal/config" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"github.com/spqrcor/gofermart/internal/logger" | ||
"github.com/spqrcor/gofermart/internal/server" | ||
) | ||
|
||
func main() { | ||
config.Init() | ||
logger.Init() | ||
db.Init() | ||
|
||
server.Start() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
module github.com/spqrcor/gofermart | ||
|
||
go 1.22 | ||
|
||
require ( | ||
filippo.io/edwards25519 v1.1.0 // indirect | ||
github.com/ClickHouse/ch-go v0.62.0 // indirect | ||
github.com/ClickHouse/clickhouse-go/v2 v2.28.1 // indirect | ||
github.com/andybalholm/brotli v1.1.0 // indirect | ||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect | ||
github.com/coder/websocket v1.8.12 // indirect | ||
github.com/dustin/go-humanize v1.0.1 // indirect | ||
github.com/elastic/go-sysinfo v1.14.1 // indirect | ||
github.com/elastic/go-windows v1.0.2 // indirect | ||
github.com/go-chi/chi/v5 v5.1.0 // indirect | ||
github.com/go-faster/city v1.0.1 // indirect | ||
github.com/go-faster/errors v0.7.1 // indirect | ||
github.com/go-sql-driver/mysql v1.8.1 // indirect | ||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect | ||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect | ||
github.com/golang-sql/sqlexp v0.1.0 // indirect | ||
github.com/golang/protobuf v1.5.4 // indirect | ||
github.com/google/uuid v1.6.0 // indirect | ||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect | ||
github.com/jackc/pgpassfile v1.0.0 // indirect | ||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect | ||
github.com/jackc/pgx/v5 v5.6.0 // indirect | ||
github.com/jackc/puddle/v2 v2.2.1 // indirect | ||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect | ||
github.com/jonboulle/clockwork v0.4.0 // indirect | ||
github.com/klauspost/compress v1.17.9 // indirect | ||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240721121621-c0bdc870f11c // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mfridman/interpolate v0.0.2 // indirect | ||
github.com/microsoft/go-mssqldb v1.7.2 // indirect | ||
github.com/ncruces/go-strftime v0.1.9 // indirect | ||
github.com/paulmach/orb v0.11.1 // indirect | ||
github.com/pierrec/lz4/v4 v4.1.21 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
github.com/pressly/goose/v3 v3.21.1 // indirect | ||
github.com/prometheus/procfs v0.15.1 // indirect | ||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect | ||
github.com/segmentio/asm v1.2.0 // indirect | ||
github.com/sethvargo/go-retry v0.3.0 // indirect | ||
github.com/shopspring/decimal v1.4.0 // indirect | ||
github.com/tursodatabase/libsql-client-go v0.0.0-20240812094001-348a4e45b535 // indirect | ||
github.com/vertica/vertica-sql-go v1.3.3 // indirect | ||
github.com/ydb-platform/ydb-go-genproto v0.0.0-20240821162910-6cb364b2ccc8 // indirect | ||
github.com/ydb-platform/ydb-go-sdk/v3 v3.77.1 // indirect | ||
github.com/ziutek/mymysql v1.5.4 // indirect | ||
go.opentelemetry.io/otel v1.29.0 // indirect | ||
go.opentelemetry.io/otel/trace v1.29.0 // indirect | ||
go.uber.org/multierr v1.11.0 // indirect | ||
go.uber.org/zap v1.27.0 // indirect | ||
golang.org/x/crypto v0.26.0 // indirect | ||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect | ||
golang.org/x/net v0.28.0 // indirect | ||
golang.org/x/sync v0.8.0 // indirect | ||
golang.org/x/sys v0.24.0 // indirect | ||
golang.org/x/text v0.17.0 // indirect | ||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect | ||
google.golang.org/grpc v1.66.0 // indirect | ||
google.golang.org/protobuf v1.34.2 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
howett.net/plist v1.0.1 // indirect | ||
modernc.org/gc/v3 v3.0.0-20240801135723-a856999a2e4a // indirect | ||
modernc.org/libc v1.60.0 // indirect | ||
modernc.org/mathutil v1.6.0 // indirect | ||
modernc.org/memory v1.8.0 // indirect | ||
modernc.org/sqlite v1.32.0 // indirect | ||
modernc.org/strutil v1.2.0 // indirect | ||
modernc.org/token v1.1.0 // indirect | ||
nhooyr.io/websocket v1.8.17 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/google/uuid" | ||
"github.com/spqrcor/gofermart/internal/authenticate" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"time" | ||
) | ||
|
||
var ErrOrderAnotherUserExists = fmt.Errorf("order another user exists") | ||
var ErrOrderUserExists = fmt.Errorf("order user exists") | ||
var ErrOrderInvalidFormat = fmt.Errorf("order invalid format") | ||
|
||
func AddOrder(ctx context.Context, orderNum string) error { | ||
if !isNumberValid(orderNum) { | ||
return ErrOrderInvalidFormat | ||
} | ||
|
||
var baseUserID, baseOrderID string | ||
orderId := uuid.NewString() | ||
userId := ctx.Value(authenticate.ContextUserID) | ||
childCtx, cancel := context.WithTimeout(ctx, time.Second*3) | ||
defer cancel() | ||
err := db.Source.QueryRowContext(childCtx, "INSERT INTO orders (id, user_id, number) VALUES ($1, $2, $3) "+ | ||
"ON CONFLICT(number) DO UPDATE SET number = EXCLUDED.number RETURNING id, user_id", orderId, userId, orderNum).Scan(&baseOrderID, &baseUserID) | ||
if err != nil { | ||
return err | ||
} else if userId != uuid.MustParse(baseUserID) { | ||
return ErrOrderAnotherUserExists | ||
} else if orderId != baseOrderID { | ||
return ErrOrderUserExists | ||
} | ||
return nil | ||
} | ||
|
||
func isNumberValid(orderNum string) bool { | ||
total := 0 | ||
isSecondDigit := false | ||
for i := len(orderNum) - 1; i >= 0; i-- { | ||
digit := int(orderNum[i] - '0') | ||
if isSecondDigit { | ||
digit *= 2 | ||
if digit > 9 { | ||
digit -= 9 | ||
} | ||
} | ||
total += digit | ||
isSecondDigit = !isSecondDigit | ||
} | ||
return total%10 == 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"errors" | ||
"fmt" | ||
"github.com/jackc/pgx/v5/pgconn" | ||
"github.com/spqrcor/gofermart/internal/authenticate" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"time" | ||
) | ||
|
||
type InputWithdraw struct { | ||
OrderNum string `json:"order"` | ||
Sum int `json:"sum"` | ||
} | ||
|
||
var ErrBalance = fmt.Errorf("balance error") | ||
|
||
func AddWithdraw(ctx context.Context, input InputWithdraw) error { | ||
if !isNumberValid(input.OrderNum) { | ||
return ErrOrderInvalidFormat | ||
} | ||
userId := ctx.Value(authenticate.ContextUserID) | ||
|
||
childCtx, cancel := context.WithTimeout(ctx, time.Second*3) | ||
defer cancel() | ||
|
||
tx, err := db.Source.BeginTx(childCtx, nil) | ||
if err != nil { | ||
return err | ||
} | ||
_, err = tx.ExecContext(childCtx, "UPDATE users SET balance = balance - $2 WHERE id = $1", userId, input.Sum) | ||
if err != nil { | ||
_ = tx.Rollback() | ||
var pgError *pgconn.PgError | ||
if errors.As(err, &pgError) && pgError.ConstraintName == "users_balance_check" { | ||
return ErrBalance | ||
} | ||
return err | ||
} | ||
|
||
var withdrawID string | ||
err = tx.QueryRowContext(childCtx, "INSERT INTO withdraw_list (order_id, sum) SELECT id, $3 FROM orders WHERE user_id = $1 AND number = $2 RETURNING id", userId, input.OrderNum, input.Sum).Scan(&withdrawID) | ||
if err != nil { | ||
_ = tx.Rollback() | ||
if errors.Is(err, sql.ErrNoRows) { | ||
return ErrOrderInvalidFormat | ||
} | ||
return err | ||
} | ||
if err = tx.Commit(); err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"github.com/spqrcor/gofermart/internal/authenticate" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"time" | ||
) | ||
|
||
type BalanceInfo struct { | ||
Current int `json:"current"` | ||
Withdrawn int `json:"withdrawn"` | ||
} | ||
|
||
func GetBalanceInfo(ctx context.Context) (BalanceInfo, error) { | ||
balanceInfo := BalanceInfo{} | ||
childCtx, cancel := context.WithTimeout(ctx, time.Second*3) | ||
defer cancel() | ||
|
||
row := db.Source.QueryRowContext(childCtx, "SELECT balance, (SELECT COALESCE(SUM(wl.sum), 0) FROM withdraw_list wl INNER JOIN orders o ON o.id = wl.order_id WHERE o.user_id = u.id) "+ | ||
"FROM users u WHERE id = $1", ctx.Value(authenticate.ContextUserID)) | ||
if err := row.Scan(&balanceInfo.Current, &balanceInfo.Withdrawn); err != nil { | ||
return balanceInfo, errors.New("User not found") | ||
} | ||
return balanceInfo, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"github.com/spqrcor/gofermart/internal/authenticate" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"github.com/spqrcor/gofermart/internal/logger" | ||
"time" | ||
) | ||
|
||
type Order struct { | ||
Number string `json:"number"` | ||
Status string `json:"status"` | ||
Accrual int `json:"accrual,omitempty"` | ||
UploadedAt string `json:"uploaded_at"` | ||
} | ||
|
||
var ErrOrdersNotFound = fmt.Errorf("orders not found") | ||
|
||
func GetOrders(ctx context.Context) ([]Order, error) { | ||
var orders []Order | ||
childCtx, cancel := context.WithTimeout(ctx, time.Second*3) | ||
defer cancel() | ||
|
||
rows, err := db.Source.QueryContext(childCtx, "SELECT number, status, accrual, created_at FROM orders WHERE user_id = $1 ORDER BY created_at DESC", ctx.Value(authenticate.ContextUserID)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
if err := rows.Close(); err != nil { | ||
logger.Log.Error(err.Error()) | ||
} | ||
if err := rows.Err(); err != nil { | ||
logger.Log.Error(err.Error()) | ||
} | ||
}() | ||
|
||
for rows.Next() { | ||
o := Order{} | ||
var accrual sql.NullInt32 | ||
if err = rows.Scan(&o.Number, &o.Status, &accrual, &o.UploadedAt); err != nil { | ||
return nil, err | ||
} | ||
o.Accrual = int(accrual.Int32) | ||
orders = append(orders, o) | ||
} | ||
|
||
if len(orders) == 0 { | ||
return nil, ErrOrdersNotFound | ||
} | ||
return orders, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/spqrcor/gofermart/internal/authenticate" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"github.com/spqrcor/gofermart/internal/logger" | ||
"time" | ||
) | ||
|
||
type Withdraw struct { | ||
OrderNum string `json:"order"` | ||
Sum int `json:"sum"` | ||
ProcessedAt string `json:"processed_at"` | ||
} | ||
|
||
var ErrWithdrawNotFound = fmt.Errorf("withdraw not found") | ||
|
||
func GetWithdraws(ctx context.Context) ([]Withdraw, error) { | ||
var withdraws []Withdraw | ||
childCtx, cancel := context.WithTimeout(ctx, time.Second*3) | ||
defer cancel() | ||
|
||
rows, err := db.Source.QueryContext(childCtx, "SELECT o.number, wl.sum, wl.created_at FROM withdraw_list wl "+ | ||
"INNER JOIN orders o ON o.id = wl.order_id WHERE o.user_id = $1 ORDER BY wl.created_at DESC", ctx.Value(authenticate.ContextUserID)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
if err := rows.Close(); err != nil { | ||
logger.Log.Error(err.Error()) | ||
} | ||
if err := rows.Err(); err != nil { | ||
logger.Log.Error(err.Error()) | ||
} | ||
}() | ||
|
||
for rows.Next() { | ||
w := Withdraw{} | ||
if err = rows.Scan(&w.OrderNum, &w.Sum, &w.ProcessedAt); err != nil { | ||
return nil, err | ||
} | ||
withdraws = append(withdraws, w) | ||
} | ||
if len(withdraws) == 0 { | ||
return nil, ErrWithdrawNotFound | ||
} | ||
return withdraws, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package actions | ||
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"errors" | ||
"fmt" | ||
"github.com/google/uuid" | ||
"github.com/spqrcor/gofermart/internal/db" | ||
"golang.org/x/crypto/bcrypt" | ||
"time" | ||
) | ||
|
||
var ErrLogin = fmt.Errorf("login error") | ||
|
||
func Login(ctx context.Context, input InputDataUser) (uuid.UUID, error) { | ||
childCtx, cancel := context.WithTimeout(ctx, time.Second*3) | ||
defer cancel() | ||
row := db.Source.QueryRowContext(childCtx, "SELECT id, password FROM users WHERE login = $1", input.Login) | ||
|
||
var userID, password string | ||
err := row.Scan(&userID, &password) | ||
if errors.Is(err, sql.ErrNoRows) { | ||
return uuid.Nil, ErrLogin | ||
} else if err != nil { | ||
return uuid.Nil, err | ||
} | ||
|
||
if err := bcrypt.CompareHashAndPassword([]byte(password), []byte(input.Password)); err != nil { | ||
return uuid.Nil, ErrLogin | ||
} | ||
return uuid.MustParse(userID), nil | ||
} |
Oops, something went wrong.