forked from ebhomengo/niki
Merge branch 'develop' of https://git.gocasts.ir/ebhomengo/niki into mehdikeshavarz/driver(agent)/loginorregister
This commit is contained in:
commit
590ed0bb07
|
|
@ -0,0 +1,34 @@
|
||||||
|
root = "."
|
||||||
|
tmp_dir = "cmd/productapp/temp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
bin = "/entrypoint.sh"
|
||||||
|
args_bin = []
|
||||||
|
cmd = "go build -mod=mod -buildvcs=false -o ./cmd/productapp/temp/main ./cmd/productapp/"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["vendor", "cmd/productapp/temp"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = []
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go"]
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = true
|
||||||
|
poll = true
|
||||||
|
poll_interval = 1000
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = false
|
||||||
|
|
@ -30,3 +30,5 @@ logs/
|
||||||
mise.log
|
mise.log
|
||||||
|
|
||||||
curl
|
curl
|
||||||
|
|
||||||
|
cmd/**/temp/main
|
||||||
124
Makefile
124
Makefile
|
|
@ -1,54 +1,64 @@
|
||||||
# TODO: add commands for build and run in dev/produciton mode
|
# --- Variables ---
|
||||||
|
BINARY_NAME ?= niki
|
||||||
|
BUILD_DIR ?= bin
|
||||||
|
|
||||||
ROOT=$(realpath $(dir $(lastword $(MAKEFILE_LIST))))
|
# ====================================================================================
|
||||||
|
# General Go Commands
|
||||||
|
# ====================================================================================
|
||||||
|
.PHONY: start test build clean mod-tidy lint install-linter help format swagger watch
|
||||||
|
|
||||||
.PHONY: help confirm lint test format build run docker swagger watch migrate/status migrate/new migrate/up migrate/down
|
start: build
|
||||||
|
$(BUILD_DIR)/$(BINARY_NAME)
|
||||||
confirm:
|
|
||||||
@echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]
|
|
||||||
|
|
||||||
lint:
|
|
||||||
which golangci-lint || (go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.0)
|
|
||||||
golangci-lint run --config=$(ROOT)/.golangci.yml $(ROOT)/...
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
|
build:
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
go build -o $(BUILD_DIR)/$(BINARY_NAME) main.go
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)/
|
||||||
|
|
||||||
|
mod-tidy:
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
install-linter:
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@which gofumpt || (go install mvdan.cc/gofumpt@latest)
|
@which gofumpt || (go install mvdan.cc/gofumpt@latest)
|
||||||
@gofumpt -l -w $(ROOT)
|
@gofumpt -l -w .
|
||||||
@which gci || (go install github.com/daixiang0/gci@latest)
|
@which gci || (go install github.com/daixiang0/gci@latest)
|
||||||
@gci write $(ROOT) --skip-generated --skip-vendor
|
@gci write . --skip-generated --skip-vendor
|
||||||
@which golangci-lint || (go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.0)
|
|
||||||
@golangci-lint run --fix
|
@golangci-lint run --fix
|
||||||
|
|
||||||
build:
|
|
||||||
go build -o niki main.go
|
|
||||||
|
|
||||||
run:
|
|
||||||
go run main.go --migrate
|
|
||||||
|
|
||||||
docker:
|
|
||||||
sudo docker compose up -d
|
|
||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
swag init
|
swag init
|
||||||
|
|
||||||
# Live Reload
|
# Live Reload
|
||||||
watch:
|
watch:
|
||||||
@if command -v CompileDaemon > /dev/null; then \
|
@if command -v CompileDaemon > /dev/null; then \
|
||||||
CompileDaemon -exclude-dir=.git -exclude=".#*" -command="./niki"; \
|
CompileDaemon -exclude-dir=.git -exclude=".#*" -command="./$(BUILD_DIR)/$(BINARY_NAME)"; \
|
||||||
else \
|
else \
|
||||||
read -p "Go's 'CompileDaemon' is not installed on your machine. Do you want to install it? [Y/n] " choice; \
|
read -p "Go's 'CompileDaemon' is not installed. Do you want to install it? [Y/n] " choice; \
|
||||||
if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \
|
if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \
|
||||||
go install github.com/githubnemo/CompileDaemon@latest; \
|
go install github.com/githubnemo/CompileDaemon@latest; \
|
||||||
CompileDaemon -exclude-dir=.git -exclude=".#*" -command="./niki"; \
|
CompileDaemon -exclude-dir=.git -exclude=".#*" -command="./$(BUILD_DIR)/$(BINARY_NAME)"; \
|
||||||
else \
|
else \
|
||||||
echo "You chose not to install CompileDaemon. Exiting..."; \
|
echo "You chose not to install CompileDaemon. Exiting..."; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ====================================================================================
|
||||||
|
# Database Migration Commands (legacy niki-core)
|
||||||
|
# ====================================================================================
|
||||||
|
.PHONY: migrate/status migrate/new migrate/up migrate/down
|
||||||
|
|
||||||
migrate/status:
|
migrate/status:
|
||||||
@sql-migrate status -env="production" -config=repository/mysql/dbconfig.yml
|
@sql-migrate status -env="production" -config=repository/mysql/dbconfig.yml
|
||||||
|
|
||||||
|
|
@ -64,3 +74,67 @@ migrate/up: confirm
|
||||||
migrate/down: confirm
|
migrate/down: confirm
|
||||||
@echo 'Tearing down last migration...'
|
@echo 'Tearing down last migration...'
|
||||||
@sql-migrate down -env="production" -config=repository/mysql/dbconfig.yml -limit=1
|
@sql-migrate down -env="production" -config=repository/mysql/dbconfig.yml -limit=1
|
||||||
|
|
||||||
|
confirm:
|
||||||
|
@echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]
|
||||||
|
|
||||||
|
# ====================================================================================
|
||||||
|
# Productapp Service Commands
|
||||||
|
# ====================================================================================
|
||||||
|
.PHONY: productapp-up productapp-up-logs productapp-rebuild productapp-watch
|
||||||
|
|
||||||
|
PRODUCTAPP_ENV = deploy/productapp/development/.env
|
||||||
|
PRODUCTAPP_COMPOSE = deploy/productapp/development/docker-compose.yml
|
||||||
|
|
||||||
|
productapp-up:
|
||||||
|
@echo "Building and starting productapp full stack..."
|
||||||
|
docker compose --env-file $(PRODUCTAPP_ENV) -f $(PRODUCTAPP_COMPOSE) up --build -d
|
||||||
|
@echo "productapp stack is up."
|
||||||
|
|
||||||
|
productapp-down:
|
||||||
|
@echo "Building and starting productapp full stack..."
|
||||||
|
docker compose --env-file $(PRODUCTAPP_ENV) -f $(PRODUCTAPP_COMPOSE) down
|
||||||
|
@echo "productapp stack is up."
|
||||||
|
|
||||||
|
productapp-up-logs:
|
||||||
|
@echo "Building and starting productapp full stack..."
|
||||||
|
docker compose --env-file $(PRODUCTAPP_ENV) -f $(PRODUCTAPP_COMPOSE) up --build
|
||||||
|
|
||||||
|
# ====================================================================================
|
||||||
|
# Docker Commands (legacy)
|
||||||
|
# ====================================================================================
|
||||||
|
.PHONY: docker
|
||||||
|
|
||||||
|
docker:
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# ====================================================================================
|
||||||
|
# Help Target
|
||||||
|
# ====================================================================================
|
||||||
|
help:
|
||||||
|
@echo "Available targets:"
|
||||||
|
@echo ""
|
||||||
|
@echo "General Go Commands:"
|
||||||
|
@echo " start - Build and run niki-core locally"
|
||||||
|
@echo " test - Run tests"
|
||||||
|
@echo " build - Compile binary"
|
||||||
|
@echo " clean - Remove build artifacts"
|
||||||
|
@echo " mod-tidy - Clean up dependencies"
|
||||||
|
@echo " format - Format code (gofumpt + gci + lint fix)"
|
||||||
|
@echo " lint - Run linters"
|
||||||
|
@echo " install-linter - Install golangci-lint"
|
||||||
|
@echo " swagger - Generate swagger docs"
|
||||||
|
@echo " watch - Live reload with CompileDaemon"
|
||||||
|
@echo ""
|
||||||
|
@echo "Database Migration (niki-core):"
|
||||||
|
@echo " migrate/status - Show migration status"
|
||||||
|
@echo " migrate/new - Create new migration (name=<name>)"
|
||||||
|
@echo " migrate/up - Run migrations up"
|
||||||
|
@echo " migrate/down - Rollback last migration"
|
||||||
|
@echo ""
|
||||||
|
@echo "Productapp Service:"
|
||||||
|
@echo " productapp-up - Build and start full stack in background"
|
||||||
|
@echo " productapp-up-logs - Build and start full stack with logs"
|
||||||
|
@echo ""
|
||||||
|
@echo "Docker (legacy):"
|
||||||
|
@echo " docker - Start docker-compose services"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "payment",
|
||||||
|
Short: "Payment service",
|
||||||
|
Long: "Payment service CLI",
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
paymentapp "git.gocasts.ir/ebhomengo/niki/paymentapp"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configPath string
|
||||||
|
|
||||||
|
var serveCmd = &cobra.Command{
|
||||||
|
Use: "serve",
|
||||||
|
Short: "start payment service",
|
||||||
|
RunE: startServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
serveCmd.Flags().StringVar(
|
||||||
|
&configPath,
|
||||||
|
"config",
|
||||||
|
"deploy/payment/development/config.yaml",
|
||||||
|
"config file path",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(serveCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func startServer(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// load config
|
||||||
|
// -------------------------
|
||||||
|
//TODO --chage to Loader
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("🚩 read config error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg paymentapp.Config
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return fmt.Errorf("🚩 parse config error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// connect database
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
dbConn, err := database.Connect(database.Config{
|
||||||
|
Port: cfg.Postgres.Port,
|
||||||
|
Host: cfg.Postgres.Host,
|
||||||
|
Username: cfg.Postgres.User,
|
||||||
|
DBName: cfg.Postgres.DbName,
|
||||||
|
Password: cfg.Postgres.Password,
|
||||||
|
Driver: cfg.Postgres.Driver,
|
||||||
|
SSLMode: cfg.Postgres.SSLMode,
|
||||||
|
MaxIdleConns: cfg.Postgres.MaxIdleConns,
|
||||||
|
MaxOpenConns: cfg.Postgres.MaxOpenConns,
|
||||||
|
ConnMaxLifetime: cfg.Postgres.ConnMaxLifetime,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer database.Close(dbConn.DB)
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// context
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// setup app
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
app, err := paymentapp.Setup(ctx, cfg, dbConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// start server
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := app.Start(); err != nil {
|
||||||
|
fmt.Println("🚩 server error:", err)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Println("payment service started 🏃➡️")
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// shutdown
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
<-sigChan
|
||||||
|
|
||||||
|
fmt.Println("shutting down 🥱...")
|
||||||
|
|
||||||
|
shutdownCtx, shutdownCancel := context.WithTimeout(
|
||||||
|
context.Background(),
|
||||||
|
10*time.Second,
|
||||||
|
)
|
||||||
|
defer shutdownCancel()
|
||||||
|
|
||||||
|
return app.Stop(shutdownCtx)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/cmd/payment/command"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
command.Execute()
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/productapp"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,27 +28,53 @@ var serveCmd = &cobra.Command{
|
||||||
func serve() {
|
func serve() {
|
||||||
log.Println("Product Service Starting...")
|
log.Println("Product Service Starting...")
|
||||||
|
|
||||||
// TODO: Initialize database connection
|
cfg := productapp.Config{
|
||||||
// TODO: Initialize service dependencies
|
HTTPServer: productapp.HTTPServerConfig{
|
||||||
// TODO: Setup HTTP server with routes
|
Port: getEnvInt("HTTP_PORT", 8080),
|
||||||
|
},
|
||||||
|
Database: mysql.Config{
|
||||||
|
Username: getEnv("DB_USERNAME", "root"),
|
||||||
|
Password: getEnv("DB_PASSWORD", ""),
|
||||||
|
Port: getEnvInt("DB_PORT", 3306),
|
||||||
|
Host: getEnv("DB_HOST", "localhost"),
|
||||||
|
DBName: getEnv("DB_NAME", "niki_db"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, err := strconv.Atoi(port); err == nil && p > 0 {
|
||||||
|
cfg.HTTPServer.Port = p
|
||||||
|
}
|
||||||
|
|
||||||
|
app := productapp.Setup(cfg)
|
||||||
|
|
||||||
// Setup graceful shutdown
|
|
||||||
go func() {
|
go func() {
|
||||||
sigCh := make(chan os.Signal, 1)
|
app.Start()
|
||||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
||||||
<-sigCh
|
|
||||||
log.Println("Shutting down Product Service gracefully...")
|
|
||||||
os.Exit(0)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
quit := make(chan os.Signal, 1)
|
||||||
fmt.Fprintf(w, "Product Service OK!")
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
})
|
<-quit
|
||||||
|
|
||||||
log.Printf("Product Service listening on port %s", port)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
defer cancel()
|
||||||
log.Fatalf("Failed to start server: %v", err)
|
|
||||||
|
if err := app.Stop(ctx); err != nil {
|
||||||
|
log.Fatalf("Server shutdown error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println("Product Service stopped.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvInt(key string, defaultValue int) int {
|
||||||
|
val := os.Getenv(key)
|
||||||
|
if val == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
http:
|
||||||
|
addr: ":9090" #--
|
||||||
|
host: "localhost"
|
||||||
|
port: 9090
|
||||||
|
read_timeout: 5
|
||||||
|
write_timeout: 10
|
||||||
|
idle_timeout: 60
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
host: "localhost"
|
||||||
|
port: 5432
|
||||||
|
driver: "postgres"
|
||||||
|
user: h1user
|
||||||
|
password: h1pass
|
||||||
|
dbName: h1db
|
||||||
|
sslMode: disable
|
||||||
|
maxIdleConns: 15
|
||||||
|
maxOpenConns: 100
|
||||||
|
connMaxLifetime: 5
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
container_name: h1-app
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "9090:9090"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
environment:
|
||||||
|
CONFIG_PATH: "/app/config.yaml"
|
||||||
|
volumes:
|
||||||
|
- ./config.yaml:/app/config.yaml
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: h1-postgres
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: h1user
|
||||||
|
POSTGRES_PASSWORD: h1pass
|
||||||
|
POSTGRES_DB: h1db
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
ARG GO_IMAGE_NAME
|
||||||
|
ARG GO_IMAGE_VERSION
|
||||||
|
|
||||||
|
FROM ${GO_IMAGE_NAME}:${GO_IMAGE_VERSION}
|
||||||
|
ENV GOPROXY=https://package-mirror.liara.ir/repository/go/
|
||||||
|
ENV GOSUMDB=off
|
||||||
|
|
||||||
|
WORKDIR /home/app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
RUN go install github.com/air-verse/air@latest
|
||||||
|
|
||||||
|
RUN printf '#!/bin/sh\n./cmd/productapp/temp/main migrate --up\nexec ./cmd/productapp/temp/main serve\n' > /entrypoint.sh && chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
CMD ["air", "-c", "/home/app/.air/.air.productapp.toml"]
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
services:
|
||||||
|
productapp-mysql:
|
||||||
|
image: mirror2.chabokan.net/mysql:8.0
|
||||||
|
container_name: productapp-mysql
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3307:3306"
|
||||||
|
volumes:
|
||||||
|
- productapp-mysql-data:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: niki_db
|
||||||
|
MYSQL_ROOT_PASSWORD: secret
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
productapp-mysql-data:
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
services:
|
||||||
|
productapp-app:
|
||||||
|
build:
|
||||||
|
context: ../../..
|
||||||
|
dockerfile: deploy/productapp/development/Dockerfile
|
||||||
|
args:
|
||||||
|
GO_IMAGE_NAME: ${GO_IMAGE_NAME}
|
||||||
|
GO_IMAGE_VERSION: ${GO_IMAGE_VERSION}
|
||||||
|
container_name: productapp-app
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ../../..:/home/app
|
||||||
|
environment:
|
||||||
|
DB_HOST: productapp-mysql
|
||||||
|
DB_USERNAME: root
|
||||||
|
DB_PASSWORD: secret
|
||||||
|
DB_NAME: niki_db
|
||||||
|
MIGRATION_PATH: /home/app/productapp/repository/migrations
|
||||||
|
depends_on:
|
||||||
|
productapp-mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
productapp-mysql:
|
||||||
|
image: mirror2.chabokan.net/mysql:8.0
|
||||||
|
container_name: productapp-mysql
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "3307:3306"
|
||||||
|
volumes:
|
||||||
|
- productapp-mysql-data:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: niki_db
|
||||||
|
MYSQL_ROOT_PASSWORD: secret
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
productapp-mysql-data:
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
package domain
|
package entity
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
type ID uint64
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type CampaignStatus string
|
type CampaignStatus string
|
||||||
|
|
||||||
|
|
@ -15,7 +16,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Campaign struct {
|
type Campaign struct {
|
||||||
ID ID `json:"id"`
|
ID types.ID `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
GoalAmount float64 `json:"goal_amount"`
|
GoalAmount float64 `json:"goal_amount"`
|
||||||
|
|
@ -23,7 +24,7 @@ type Campaign struct {
|
||||||
Status CampaignStatus `json:"status"`
|
Status CampaignStatus `json:"status"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
AdminID ID `json:"creator_id"`
|
AdminID types.ID `json:"creator_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Behavior
|
// Behavior
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/campaign/entity"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
conn *mysql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *mysql.DB) *DB {
|
||||||
|
return &DB{conn: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCampaign creates a new campaign
|
||||||
|
func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types.ID, error) {
|
||||||
|
const Op = "repository.mysql.campaign.create"
|
||||||
|
|
||||||
|
tx, err := d.conn.Conn().BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
query := `INSERT INTO campaigns (title, description, goal_amount, raised_amount,
|
||||||
|
status, deadline_at ,admin_id , created_at )
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )`
|
||||||
|
|
||||||
|
result, err := tx.ExecContext(ctx, query,
|
||||||
|
campaign.Title,
|
||||||
|
campaign.Description,
|
||||||
|
campaign.GoalAmount,
|
||||||
|
campaign.RaisedAmount,
|
||||||
|
campaign.Status,
|
||||||
|
campaign.DeadlineAt,
|
||||||
|
campaign.AdminID,
|
||||||
|
campaign.CreatedAt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
campaignID, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ID(campaignID), nil
|
||||||
|
}
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package mysql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/campaign/entity"
|
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type CampaignRepository interface {
|
|
||||||
CreateAndSave(ctx context.Context, campaign *Campaign) error
|
|
||||||
FindByID(ctx context.Context, id ID) (*Campaign, error)
|
|
||||||
List(ctx context.Context, status CampaignStatus, limit, offset int) ([]*Campaign, error)
|
|
||||||
Delete(ctx context.Context, id ID) error
|
|
||||||
Update(ctx context.Context, campaign *Campaign) error
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
@ -1,48 +1,50 @@
|
||||||
package campaign
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors" // For standard errors
|
"fmt"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/campaign/entity"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"your_project/domain"
|
|
||||||
"your_project/pkg/richerror"
|
|
||||||
"your_project/repository"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ID = unit64
|
// CreateCampaign handles creation of a new campaign.
|
||||||
type EntityCampaign = domain.Campaign
|
func (s *CampaignService) CreateCampaign(ctx context.Context, req entity.Campaign) (types.ID, error) {
|
||||||
|
const op = "service.campaign.create_campaign"
|
||||||
|
|
||||||
|
if err := validateCreateCampaignRequest(req); err != nil {
|
||||||
type CampaignServiceImp interface {
|
return 0, richerror.New(op).WithErr(err)
|
||||||
CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type CampaignService struct {
|
|
||||||
repo repository.CampaignRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCampaignService(
|
|
||||||
campaignRepo repository.CampaignRepository,
|
|
||||||
) *CampaignService {
|
|
||||||
return &CampaignService{
|
|
||||||
repo: campaignRepo,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
campaign := entity.Campaign{
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
GoalAmount: req.GoalAmount,
|
||||||
|
RaisedAmount: 0,
|
||||||
|
Status: req.Status,
|
||||||
|
DeadlineAt: req.DeadlineAt,
|
||||||
|
AdminID: req.AdminID,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := s.repo.Create(ctx, campaign)
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateCreateCampaignRequest(req entity.Campaign) error {
|
||||||
func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaignRequest) (ID, error) {
|
|
||||||
const Op = "service.campaign.create_campaign"
|
|
||||||
|
|
||||||
if req.Title == "" {
|
if req.Title == "" {
|
||||||
return 0, richerror.New(Op).WithMessage("title is required")
|
return errRequired("title")
|
||||||
}
|
}
|
||||||
if req.GoalAmount <= 0 {
|
if req.GoalAmount <= 0 {
|
||||||
return 0, richerror.New(Op).WithMessage("goal_amount must be greater than 0")
|
return errInvalid("goal_amount must be greater than 0")
|
||||||
}
|
}
|
||||||
if req.AdminID == 0 {
|
if req.AdminID == 0 {
|
||||||
return 0, richerror.New(Op).WithMessage("admin_id is required")
|
return errRequired("admin_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
validStatuses := map[string]bool{
|
validStatuses := map[string]bool{
|
||||||
|
|
@ -50,32 +52,20 @@ func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaign
|
||||||
"active": true,
|
"active": true,
|
||||||
"completed": true,
|
"completed": true,
|
||||||
"cancelled": true,
|
"cancelled": true,
|
||||||
"paused": true
|
"paused": true,
|
||||||
}
|
}
|
||||||
if !validStatuses[req.Status] {
|
if !validStatuses[string(req.Status)] {
|
||||||
return 0, richerror.New(Op).WithMessage("invalid status provided")
|
return errInvalid("invalid status provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
newCampaign := &EntityCampaign{
|
|
||||||
Title: req.Title,
|
|
||||||
Description: req.Description,
|
|
||||||
GoalAmount: req.GoalAmount,
|
|
||||||
RaisedAmount: 0, // Initially 0
|
|
||||||
Status: EntityCampaign.Status(req.Status),
|
|
||||||
DeadlineAt: req.DeadlineAt,
|
|
||||||
AdminID: req.AdminID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
createdCampaignID, err := s.repo.CreateAndSave(ctx, newCampaign)
|
|
||||||
if err != nil {
|
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return createdCampaignID, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Helpers ---
|
||||||
|
func errRequired(field string) error {
|
||||||
|
return fmt.Errorf("%s is required", field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errInvalid(msg string) error {
|
||||||
|
return fmt.Errorf(msg)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCampaignResponse struct {
|
||||||
|
ID types.ID `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddCampaignRequest struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
GoalAmount float64 `json:"goal_amount"`
|
||||||
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
|
AdminID types.ID `json:"admin_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCampaignRequest struct {
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
GoalAmount *float64 `json:"goal_amount,omitempty"`
|
||||||
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
|
Status *string `json:"status,omitempty"` // draft/active/completed/paused/cancelled
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "fmt"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/campaign/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CampaignFilterParam struct {
|
||||||
|
AdminID types.ID
|
||||||
|
Status string
|
||||||
|
//nil true false
|
||||||
|
IsArchived *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CampaignStorage interface {
|
||||||
|
Create(ctx context.Context, c entity.Campaign) (types.ID, error) // باید ID برگرداند
|
||||||
|
Update(ctx context.Context, c entity.Campaign) error
|
||||||
|
FindByID(ctx context.Context, id types.ID) (entity.Campaign, error)
|
||||||
|
FindAll(ctx context.Context, filter CampaignFilterParam) ([]entity.Campaign, error)
|
||||||
|
Archive(ctx context.Context, id types.ID) error // instead Delete
|
||||||
|
TotalDonations(ctx context.Context, campaignID types.ID) (int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CampaignService struct {
|
||||||
|
repo CampaignStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCampaignService constructs a new CampaignService.
|
||||||
|
func NewCampaignService(storage CampaignStorage) *CampaignService {
|
||||||
|
return &CampaignService{
|
||||||
|
repo: storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
conn *mysql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *mysql.DB) *DB {
|
||||||
|
return &DB{conn: db}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package mysql
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo interface {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gateway struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
Code string
|
||||||
|
IsActive bool
|
||||||
|
Config json.RawMessage
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Currency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CurrencyIRR Currency = "IRR"
|
||||||
|
CurrencyUSD Currency = "USD"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PaymentStatusPending PaymentStatus = "Pending"
|
||||||
|
PaymentStatusSuccess PaymentStatus = "Success"
|
||||||
|
PaymentStatusFailed PaymentStatus = "Failed"
|
||||||
|
PaymentStatusCancelled PaymentStatus = "Cancelled"
|
||||||
|
//...
|
||||||
|
)
|
||||||
|
|
||||||
|
type PayableType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PayableTypeDonate PayableType = "Donate"
|
||||||
|
PayableTypeOrder PayableType = "Order"
|
||||||
|
PayableTypeWalet PayableType = "WaletCharge"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Payment struct {
|
||||||
|
ID uint
|
||||||
|
UserID uint
|
||||||
|
MethodID uint
|
||||||
|
GatewayID uint
|
||||||
|
PayableType PayableType
|
||||||
|
PayableID uint
|
||||||
|
TotalAmount int64
|
||||||
|
PaidAmount int64
|
||||||
|
Currency Currency
|
||||||
|
Status PaymentStatus
|
||||||
|
Description string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
PaidAt *time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type PaymentMethod struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
IsActive bool
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransactionTypeRequest TransactionType = "request"
|
||||||
|
TransactionTypeVerify TransactionType = "verify"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransactionStatusPending = "Pending"
|
||||||
|
TransactionStatusSuccess = "Success"
|
||||||
|
TransactionStatusFailed = "Failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentTransaction struct {
|
||||||
|
ID uint
|
||||||
|
PaymentID uint
|
||||||
|
Type TransactionType
|
||||||
|
RequestData json.RawMessage
|
||||||
|
ResponseData json.RawMessage
|
||||||
|
RefID string
|
||||||
|
Status TransactionStatus
|
||||||
|
GatewayToken string
|
||||||
|
ErrorMessage string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package gateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gatewayFactoryImpl struct{}
|
||||||
|
|
||||||
|
func NewGatewayFactory() service.GatewayFactory {
|
||||||
|
return &gatewayFactoryImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f gatewayFactoryImpl) GetGatewayAdapter(code string) (service.GatewayPort, error) {
|
||||||
|
switch code {
|
||||||
|
case "melat":
|
||||||
|
return nil, nil
|
||||||
|
case "zarinpal":
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Gateway Code(%s) is wrong!", code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package gateway
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentRepository struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePayment implements [service.PaymentRepo].
|
||||||
|
func (*PaymentRepository) CreatePayment(ctx context.Context, p *entity.Payment) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTransaction implements [service.PaymentRepo].
|
||||||
|
func (p *PaymentRepository) CreateTransaction(ctx context.Context, t *entity.PaymentTransaction) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTransaction implements [service.PaymentRepo].
|
||||||
|
func (p *PaymentRepository) UpdateTransaction(ctx context.Context, t *entity.PaymentTransaction) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPaymentRepository(db *sql.DB) *PaymentRepository {
|
||||||
|
return &PaymentRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentMethodRepository struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGatewayByCode implements [service.PaymentMethodRepo].
|
||||||
|
func (p *PaymentMethodRepository) GetGatewayByCode(ctx context.Context, code string) (entity.Gateway, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPaymentMethodRepository(db *sql.DB) *PaymentMethodRepository {
|
||||||
|
return &PaymentMethodRepository{
|
||||||
|
DB: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package database
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/domain/payment/entity"
|
||||||
|
|
||||||
|
type InitiatePaymentRequest struct {
|
||||||
|
UserID uint
|
||||||
|
PayableType entity.PayableType
|
||||||
|
PayableID uint
|
||||||
|
GatewayCode string
|
||||||
|
CallbackURL string
|
||||||
|
Amount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitiatePaymentResponse struct {
|
||||||
|
PaymentID uint
|
||||||
|
RedirectURL string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentService struct {
|
||||||
|
paymentRepo PaymentRepo
|
||||||
|
paymentMethodRepo PaymentMethodRepo
|
||||||
|
gwFactory GatewayFactory
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentRepo interface {
|
||||||
|
CreatePayment(ctx context.Context, p *entity.Payment) error
|
||||||
|
CreateTransaction(ctx context.Context, t *entity.PaymentTransaction) error
|
||||||
|
UpdateTransaction(ctx context.Context, t *entity.PaymentTransaction) error
|
||||||
|
}
|
||||||
|
type PaymentMethodRepo interface {
|
||||||
|
GetGatewayByCode(ctx context.Context, code string) (entity.Gateway, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GatewayFactory interface {
|
||||||
|
GetGatewayAdapter(code string) (GatewayPort, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GatewayPort interface {
|
||||||
|
Request(amount int64, callbackURL string, description string) (token string, redirectURL string, rawReq []byte, rawRes []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPaymentService(pr PaymentRepo, pmr PaymentMethodRepo, gwf GatewayFactory) *PaymentService {
|
||||||
|
return &PaymentService{
|
||||||
|
paymentRepo: pr,
|
||||||
|
paymentMethodRepo: pmr,
|
||||||
|
gwFactory: gwf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PaymentService) InitiatePayment(ctx context.Context, req InitiatePaymentRequest) (*InitiatePaymentResponse, error) {
|
||||||
|
|
||||||
|
gateway, err := s.paymentMethodRepo.GetGatewayByCode(ctx, req.GatewayCode)
|
||||||
|
if err != nil || !gateway.IsActive {
|
||||||
|
return nil, errors.New("gateway is not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
payment := &entity.Payment{
|
||||||
|
UserID: req.UserID,
|
||||||
|
PayableType: req.PayableType,
|
||||||
|
PayableID: req.PayableID,
|
||||||
|
GatewayID: gateway.ID,
|
||||||
|
TotalAmount: req.Amount,
|
||||||
|
Currency: entity.CurrencyIRR,
|
||||||
|
Status: entity.PaymentStatusPending,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
if err := s.paymentRepo.CreatePayment(ctx, payment); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction := &entity.PaymentTransaction{
|
||||||
|
PaymentID: payment.ID,
|
||||||
|
Type: entity.TransactionTypeRequest,
|
||||||
|
Status: entity.TransactionStatusPending,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
if err := s.paymentRepo.CreateTransaction(ctx, transaction); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create initial transaction: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gwAdapter, err := s.gwFactory.GetGatewayAdapter(gateway.Code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load gateway adapter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := fmt.Sprintf(" پرداخت برای %s شماره %d", req.PayableType, req.PayableID)
|
||||||
|
token, redirectURL, rawReq, rawRes, gwErr := gwAdapter.Request(req.Amount, req.CallbackURL, desc)
|
||||||
|
|
||||||
|
transaction.RequestData = rawReq
|
||||||
|
transaction.ResponseData = rawRes
|
||||||
|
transaction.UpdatedAt = time.Now()
|
||||||
|
|
||||||
|
if gwErr != nil {
|
||||||
|
transaction.Status = entity.TransactionStatusFailed
|
||||||
|
transaction.ErrorMessage = gwErr.Error()
|
||||||
|
_ = s.paymentRepo.UpdateTransaction(ctx, transaction)
|
||||||
|
return nil, fmt.Errorf("gateway request failed: %w", gwErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Status = entity.TransactionStatusSuccess
|
||||||
|
transaction.GatewayToken = token
|
||||||
|
if err := s.paymentRepo.UpdateTransaction(ctx, transaction); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update transaction with token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &InitiatePaymentResponse{
|
||||||
|
PaymentID: payment.ID,
|
||||||
|
RedirectURL: redirectURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
type PaymentMethodService struct {
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package cart
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity"
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
"git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -43,7 +43,7 @@ func (r Repo) itemKey(productID types.ID) string {
|
||||||
return fmt.Sprintf("item:%d", productID)
|
return fmt.Sprintf("item:%d", productID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Repo) AddItem(ctx context.Context, userID types.ID, item cart.Item) error {
|
func (r Repo) AddItem(ctx context.Context, userID types.ID, item entity.Item) error {
|
||||||
const op = "shoppingbasketapp.repository.AddItem"
|
const op = "shoppingbasketapp.repository.AddItem"
|
||||||
|
|
||||||
cartKey := r.cartKey(userID)
|
cartKey := r.cartKey(userID)
|
||||||
|
|
@ -65,7 +65,7 @@ func (r Repo) AddItem(ctx context.Context, userID types.ID, item cart.Item) erro
|
||||||
} else {
|
} else {
|
||||||
existsItem, _ := r.client.HGet(ctx, cartKey, itemKey).Result()
|
existsItem, _ := r.client.HGet(ctx, cartKey, itemKey).Result()
|
||||||
if existsItem != "" {
|
if existsItem != "" {
|
||||||
var i cart.Item
|
var i entity.Item
|
||||||
if err := json.Unmarshal([]byte(existsItem), &i); err != nil {
|
if err := json.Unmarshal([]byte(existsItem), &i); err != nil {
|
||||||
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
@ -91,31 +91,31 @@ func parsInt(s string) int64 {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Repo) GetCart(ctx context.Context, userID types.ID) (cart.Cart, error) {
|
func (r Repo) GetCart(ctx context.Context, userID types.ID) (entity.Cart, error) {
|
||||||
const op = "shoppingbasketapp.repository.GetCart"
|
const op = "shoppingbasketapp.repository.GetCart"
|
||||||
cartKey := r.cartKey(userID)
|
cartKey := r.cartKey(userID)
|
||||||
|
|
||||||
exists, err := r.client.Exists(ctx, cartKey).Result()
|
exists, err := r.client.Exists(ctx, cartKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists == 0 {
|
if exists == 0 {
|
||||||
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket")
|
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindNotFound).WithMessage("not found shopping basket")
|
||||||
}
|
}
|
||||||
|
|
||||||
allCart, err := r.client.HGetAll(ctx, cartKey).Result()
|
allCart, err := r.client.HGetAll(ctx, cartKey).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c := cart.Cart{Items: []cart.Item{}}
|
c := entity.Cart{Items: []entity.Item{}}
|
||||||
|
|
||||||
for field, value := range allCart {
|
for field, value := range allCart {
|
||||||
if strings.HasPrefix(field, "item:") {
|
if strings.HasPrefix(field, "item:") {
|
||||||
var i cart.Item
|
var i entity.Item
|
||||||
if err := json.Unmarshal([]byte(value), &i); err != nil {
|
if err := json.Unmarshal([]byte(value), &i); err != nil {
|
||||||
return cart.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return entity.Cart{}, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Items = append(c.Items, i)
|
c.Items = append(c.Items, i)
|
||||||
|
|
@ -185,7 +185,7 @@ func (r Repo) UpdateQuantity(ctx context.Context, userID, productID types.ID, qu
|
||||||
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var item cart.Item
|
var item entity.Item
|
||||||
if err := json.Unmarshal([]byte(data), &item); err != nil {
|
if err := json.Unmarshal([]byte(data), &item); err != nil {
|
||||||
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +228,7 @@ func (r Repo) updateTotalPrice(ctx context.Context, cartKey string) error {
|
||||||
|
|
||||||
for field, value := range allFields {
|
for field, value := range allFields {
|
||||||
if strings.HasPrefix(field, "item:") {
|
if strings.HasPrefix(field, "item:") {
|
||||||
var item cart.Item
|
var item entity.Item
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(value), &item); err != nil {
|
if err := json.Unmarshal([]byte(value), &item); err != nil {
|
||||||
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package cart
|
package service
|
||||||
|
|
||||||
import "git.gocasts.ir/ebhomengo/niki/types"
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
type AddToCartRequest struct {
|
type AddToCartRequest struct {
|
||||||
UserID types.ID `json:"user_id"`
|
UserID types.ID `json:"user_id"`
|
||||||
|
|
@ -11,11 +14,11 @@ type AddToCartRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetCartResponse struct {
|
type GetCartResponse struct {
|
||||||
UserID types.ID `json:"user_id"`
|
UserID types.ID `json:"user_id"`
|
||||||
Items []Item `json:"items"`
|
Items []entity.Item `json:"items"`
|
||||||
TotalPrice types.Price `json:"total_price"`
|
TotalPrice types.Price `json:"total_price"`
|
||||||
CreatedAt int64 `json:"created_at"`
|
CreatedAt int64 `json:"created_at"`
|
||||||
ExpireAt int64 `json:"expire_at"`
|
ExpireAt int64 `json:"expire_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveFromCartRequest struct {
|
type RemoveFromCartRequest struct {
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
package cart
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/entity"
|
||||||
"git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
"git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
|
@ -9,8 +10,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
AddItem(ctx context.Context, userID types.ID, item Item) error
|
AddItem(ctx context.Context, userID types.ID, item entity.Item) error
|
||||||
GetCart(ctx context.Context, userID types.ID) (Cart, error)
|
GetCart(ctx context.Context, userID types.ID) (entity.Cart, error)
|
||||||
DeleteItem(ctx context.Context, userID, productID types.ID) error
|
DeleteItem(ctx context.Context, userID, productID types.ID) error
|
||||||
UpdateQuantity(ctx context.Context, userID, productID types.ID, quantity int) error
|
UpdateQuantity(ctx context.Context, userID, productID types.ID, quantity int) error
|
||||||
DeleteCart(ctx context.Context, userID types.ID) error
|
DeleteCart(ctx context.Context, userID types.ID) error
|
||||||
|
|
@ -33,7 +34,7 @@ func (s Service) AddToBasket(ctx context.Context, req AddToCartRequest) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.repo.AddItem(ctx, req.UserID, Item{
|
return s.repo.AddItem(ctx, req.UserID, entity.Item{
|
||||||
ProductID: req.ProductID,
|
ProductID: req.ProductID,
|
||||||
Quantity: req.Quantity,
|
Quantity: req.Quantity,
|
||||||
Price: req.Price,
|
Price: req.Price,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package cart
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
ID uint64
|
||||||
|
UserID uint64
|
||||||
|
Amount float64
|
||||||
|
Currency Currency
|
||||||
|
ActionType TransactionType
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransactionTypeDeposit TransactionType = "deposit"
|
||||||
|
TransactionTypeWithdraw TransactionType = "withdraw"
|
||||||
|
TransactionTypeRefund TransactionType = "refund"
|
||||||
|
TransactionTypeDonate TransactionType = "donate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Currency string
|
||||||
|
|
||||||
|
const (
|
||||||
|
IRR Currency = "IRR"
|
||||||
|
USD Currency = "USD"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Wallet struct {
|
||||||
|
ID uint64
|
||||||
|
UserID uint64 // user unique ID
|
||||||
|
Balance float64
|
||||||
|
Currency Currency
|
||||||
|
UpdatedAt time.Time
|
||||||
|
Status WalletStatus // "active", "frozen", "closed"
|
||||||
|
}
|
||||||
|
|
||||||
|
type WalletStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Frozen WalletStatus = "frozen" // when need to check , approve ,validate , solve sth (but deposit is possible)
|
||||||
|
Active WalletStatus = "active" // when everything is ok
|
||||||
|
|
||||||
|
// ??
|
||||||
|
// Closed WalletStatus = "closed" // when need to check , approve ,validate , solve sth (exp : security problem)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateTransactionRequest struct {
|
||||||
|
UserID uint64 `json:"user_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency entity.Currency `json:"currency"`
|
||||||
|
ActionType entity.TransactionType `json:"action_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type InsertTransactionResponse struct {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TransactionRequest struct {
|
||||||
|
UserID uint64 `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionResponse struct {
|
||||||
|
Transaction []TransactionInfo `json:"transaction"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionInfo struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
UserID uint64 `json:"user_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Currency entity.Currency `json:"currency"`
|
||||||
|
ActionType entity.TransactionType `json:"action_type"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package param
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalletRequest struct {
|
||||||
|
UserID uint64 `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WalletResponse struct {
|
||||||
|
Wallet WalletInfo `json:"wallet"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WalletInfo struct {
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Status entity.WalletStatus `json:"status"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/pkg/database/postgres"
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
conn *postgres.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(conn *postgres.DB) *DB {
|
||||||
|
return &DB{conn: conn}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
production:
|
||||||
|
dialect: postgres
|
||||||
|
datasource: "host=127.0.0.1 port=5432 user=wallet password=wallet2123 dbname=wallet_db sslmode=disable"
|
||||||
|
dir: domain/wallet/repository/postgres/migrations
|
||||||
|
table: wallet_migrationsns
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/param"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Service) CreateTransaction(ctx context.Context, request param.CreateTransactionRequest) (param.InsertTransactionResponse, error) {
|
||||||
|
|
||||||
|
const op = richerror.Op("wallet.service.CreateTransaction")
|
||||||
|
|
||||||
|
transaction := entity.Transaction{
|
||||||
|
ID: 0,
|
||||||
|
UserID: request.UserID,
|
||||||
|
Amount: request.Amount,
|
||||||
|
Currency: request.Currency,
|
||||||
|
ActionType: request.ActionType,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
err := s.repo.InsertTransaction(ctx, transaction)
|
||||||
|
if err != nil {
|
||||||
|
return param.InsertTransactionResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return param.InsertTransactionResponse{}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
GetTransactionListByUserID(ctx context.Context, UserID uint64) ([]entity.Transaction, error)
|
||||||
|
GetWalletByUserID(ctx context.Context, UserID uint64) (entity.Wallet, error)
|
||||||
|
InsertTransaction(ctx context.Context, transaction entity.Transaction) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repo Repository
|
||||||
|
cfg Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(repo Repository, cfg Config) Service {
|
||||||
|
|
||||||
|
return Service{repo: repo, cfg: cfg}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/param"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Service) GetUserTransactionHistory(ctx context.Context, request param.TransactionRequest) (param.TransactionResponse, error) {
|
||||||
|
const op = richerror.Op("wallet.service.GetUserTransactionHistory")
|
||||||
|
|
||||||
|
transactionList, err := s.repo.GetTransactionListByUserID(ctx, request.UserID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return param.TransactionResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return param.TransactionResponse{Transaction: transactionEntityToTransactionInfo(transactionList)}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func transactionEntityToTransactionInfo(TransactionList []entity.Transaction) []param.TransactionInfo {
|
||||||
|
transactionInfoList := make([]param.TransactionInfo, len(TransactionList))
|
||||||
|
for i, transaction := range TransactionList {
|
||||||
|
transactionInfoList[i] = param.TransactionInfo{
|
||||||
|
ID: transaction.ID,
|
||||||
|
UserID: transaction.UserID,
|
||||||
|
Amount: transaction.Amount,
|
||||||
|
Currency: transaction.Currency,
|
||||||
|
ActionType: transaction.ActionType,
|
||||||
|
Timestamp: transaction.Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionInfoList
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/wallet/param"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Service) GetUserWallet(ctx context.Context, request param.WalletRequest) (param.WalletResponse, error) {
|
||||||
|
const op = richerror.Op("wallet.service.GetUserWallet")
|
||||||
|
|
||||||
|
wallet, err := s.repo.GetWalletByUserID(ctx, request.UserID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return param.WalletResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return param.WalletResponse{
|
||||||
|
Wallet: param.WalletInfo{
|
||||||
|
Balance: wallet.Balance,
|
||||||
|
UpdatedAt: wallet.UpdatedAt,
|
||||||
|
Status: wallet.Status,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package donate_server
|
|
||||||
|
|
||||||
type Handler struct{}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package donate_server
|
|
||||||
|
|
||||||
import "github.com/labstack/echo/v4"
|
|
||||||
|
|
||||||
func (h Handler) RegisterRoutes(e *echo.Echo) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
package donate_server
|
|
||||||
|
|
||||||
import (
|
|
||||||
httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Server httpserver.Server
|
|
||||||
Handler Handler
|
|
||||||
Router *echo.Echo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Server) Start() {
|
|
||||||
s.Handler.RegisterRoutes(s.Router)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/campaign/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/campaign/service"
|
||||||
|
httpmsg "git.gocasts.ir/ebhomengo/niki/pkg/http_msg"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
svc service.CampaignService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(svc service.CampaignService) Handler {
|
||||||
|
return Handler{svc: svc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) createCampaign(c echo.Context) error {
|
||||||
|
|
||||||
|
var req entity.Campaign
|
||||||
|
if err := c.Bind(&req); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "invalid request body",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
req.CreatedAt = time.Now()
|
||||||
|
req.RaisedAmount = 0
|
||||||
|
|
||||||
|
createdID, err := h.svc.CreateCampaign(c.Request().Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
msg, code := httpmsg.Error(err)
|
||||||
|
c.Logger().Errorf("Service error creating campaign: %v (Code: %d)", err, code)
|
||||||
|
return c.JSON(code, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusCreated, map[string]interface{}{
|
||||||
|
"message": "campaign created successfully",
|
||||||
|
"id": createdID,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Server) healthCheck(c echo.Context) error {
|
||||||
|
return c.JSON(http.StatusOK, echo.Map{
|
||||||
|
"message": "everything is good!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
handler Handler
|
||||||
|
HTTPServer *httpserver.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(handler Handler, hS *httpserver.Server) Server {
|
||||||
|
return Server{handler: handler, HTTPServer: hS}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) Serve() error {
|
||||||
|
s.registerRoutes()
|
||||||
|
if err := s.HTTPServer.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) Stop(ctx context.Context) error {
|
||||||
|
return s.HTTPServer.Stop(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) registerRoutes() {
|
||||||
|
router := s.HTTPServer.GetRouter()
|
||||||
|
|
||||||
|
router.GET("campaign/health-check", s.healthCheck)
|
||||||
|
|
||||||
|
r := router.Group("campaign")
|
||||||
|
|
||||||
|
r.POST("/", s.handler.createCampaign)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"git.gocasts.ir/ebhomengo/niki/campaign/entity"
|
"git.gocasts.ir/ebhomengo/niki/donate_app/service/entity"
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
|
@ -16,11 +16,9 @@ func New(db *mysql.DB) *DB {
|
||||||
return &DB{conn: db}
|
return &DB{conn: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create adds a new participant to a campaign
|
||||||
|
func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) {
|
||||||
// CreateCampaign creates a new campaign
|
const Op = "repository.mysql.campaign_participant.create"
|
||||||
func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types.ID, error) {
|
|
||||||
const Op = "repository.mysql.campaign.create"
|
|
||||||
|
|
||||||
tx, err := d.conn.Conn().BeginTx(ctx, nil)
|
tx, err := d.conn.Conn().BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -28,67 +26,29 @@ func (d *DB) CreateAndSave(ctx context.Context, campaign entity.Campaign) (types
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
query := `INSERT INTO campaigns (title, description, goal_amount, raised_amount,
|
query := `INSERT INTO campaign_participants (id,campaign_id, user_id, amount , created_at)
|
||||||
status, deadline_at ,admin_id , created_at )
|
VALUES (?, ?, ? , ? , NOW())`
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ? , NOW() )`
|
|
||||||
|
|
||||||
result, err := tx.ExecContext(ctx, query,
|
result, err := tx.ExecContext(ctx, query,
|
||||||
campaign.Title,
|
participant.ID,
|
||||||
campaign.Description,
|
participant.CampaignID,
|
||||||
campaign.GoalAmount,
|
participant.UserID,
|
||||||
campaign.RaisedAmount,
|
participant.Amount,
|
||||||
campaign.Status,
|
participant.CreatedAt,
|
||||||
campaign.DeadlineAt,
|
|
||||||
campaign.AdminID,
|
|
||||||
campaign.CreatedAt,
|
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
campaignID, err := result.LastInsertId()
|
id, err := result.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.ID(campaignID), nil
|
return types.ID(id), nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create adds a new participant to a campaign
|
|
||||||
func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) {
|
|
||||||
const Op = "repository.mysql.campaign_participant.create"
|
|
||||||
|
|
||||||
query := `INSERT INTO campaign_participants (id,campaign_id, user_id, amount , created_at)
|
|
||||||
VALUES (?, ?, ? , ? , NOW())`
|
|
||||||
|
|
||||||
result, err := d.conn.ExecContext(ctx, query,
|
|
||||||
participant.ID,
|
|
||||||
participant.CampaignID,
|
|
||||||
participant.UserID,
|
|
||||||
participant.Amount,
|
|
||||||
participant.CreatedAt
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := result.LastInsertId()
|
|
||||||
if err != nil {
|
|
||||||
return 0, richerror.New(Op).WithErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.ID(id), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package service
|
package entity
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type ID uint64
|
type ID uint64
|
||||||
|
|
||||||
|
|
||||||
type CampaignParticipant struct {
|
type CampaignParticipant struct {
|
||||||
ID ID `json:"id"`
|
ID ID `json:"id"`
|
||||||
CampaignID ID `json:"campaign_id"`
|
CampaignID ID `json:"campaign_id"`
|
||||||
UserID ID `json:"user_id"`
|
UserID ID `json:"user_id"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1 +1,26 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCampaignResponse struct {
|
||||||
|
ID types.ID `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddCampaignRequest struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
GoalAmount float64 `json:"goal_amount"`
|
||||||
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
|
AdminID types.ID `json:"admin_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateCampaignRequest struct {
|
||||||
|
Title *string `json:"title,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
GoalAmount *float64 `json:"goal_amount,omitempty"`
|
||||||
|
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||||
|
Status *string `json:"status,omitempty"` // draft/active/completed/paused/cancelled
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/campaign/entity"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/repository"
|
|
||||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type CampaignService struct {
|
|
||||||
repo repository.repo
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// type CampaignServiceInterface interface {
|
|
||||||
// CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error)
|
|
||||||
// }
|
|
||||||
|
|
||||||
func NewCampaignService(
|
|
||||||
repo repository.CampaignRepository,
|
|
||||||
participantRepo repository.CampaignParticipantRepository,
|
|
||||||
) *CampaignService {
|
|
||||||
return &CampaignService{
|
|
||||||
repo: repo,
|
|
||||||
participantRepo: participantRepo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package gamificationapp
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUp(cnf Config) *Application {
|
||||||
|
return &Application{
|
||||||
|
cnf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package gamificationapp
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package gamification
|
||||||
|
|
||||||
|
import (
|
||||||
|
gamification "git.gocasts.ir/ebhomengo/niki/domain/gamification/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
GamificationSvc gamification.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(gamificationSvc gamification.Service) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
GamificationSvc: gamificationSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package gamification
|
||||||
|
|
||||||
|
import "github.com/labstack/echo/v4"
|
||||||
|
|
||||||
|
func (h Handler) SetRoutes(e *echo.Echo) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/gamificationapp/delivery/http/gamification"
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
gamificationHandler *gamification.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(handler gamification.Handler) *Server {
|
||||||
|
return &Server{
|
||||||
|
gamificationHandler: &handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
12
go.mod
12
go.mod
|
|
@ -9,10 +9,14 @@ require (
|
||||||
github.com/go-sql-driver/mysql v1.9.3
|
github.com/go-sql-driver/mysql v1.9.3
|
||||||
github.com/gocasters/rankr v0.0.0-20260222055437-aadc1fdc6a1d
|
github.com/gocasters/rankr v0.0.0-20260222055437-aadc1fdc6a1d
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20250521085720-bf793ab67800
|
||||||
github.com/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d
|
github.com/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d
|
||||||
github.com/knadh/koanf v1.5.0
|
github.com/knadh/koanf v1.5.0
|
||||||
github.com/labstack/echo-jwt/v4 v4.4.0
|
github.com/labstack/echo-jwt/v4 v4.4.0
|
||||||
github.com/labstack/echo/v4 v4.15.1
|
github.com/labstack/echo/v4 v4.15.1
|
||||||
|
github.com/labstack/gommon v0.4.2
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.19
|
||||||
github.com/ory/dockertest/v3 v3.12.0
|
github.com/ory/dockertest/v3 v3.12.0
|
||||||
github.com/redis/go-redis/v9 v9.18.0
|
github.com/redis/go-redis/v9 v9.18.0
|
||||||
github.com/rubenv/sql-migrate v1.8.1
|
github.com/rubenv/sql-migrate v1.8.1
|
||||||
|
|
@ -21,9 +25,10 @@ require (
|
||||||
github.com/swaggo/echo-swagger v1.5.2
|
github.com/swaggo/echo-swagger v1.5.2
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/swaggo/swag v1.16.6
|
||||||
golang.org/x/crypto v0.48.0
|
golang.org/x/crypto v0.48.0
|
||||||
|
google.golang.org/grpc v1.80.0
|
||||||
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
google.golang.org/grpc v1.79.2
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
google.golang.org/protobuf v1.36.10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -90,9 +95,6 @@ require (
|
||||||
golang.org/x/text v0.35.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.42.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
|
|
||||||
google.golang.org/grpc v1.80.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
|
|
||||||
30
go.sum
30
go.sum
|
|
@ -98,6 +98,10 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||||
|
|
@ -147,6 +151,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
|
@ -158,13 +164,14 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
||||||
|
|
@ -205,6 +212,8 @@ github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2
|
||||||
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20250521085720-bf793ab67800 h1:lvIuaX7hO0eO3Rlev+cVnlsoExR3i/JXxu88zt4JHPg=
|
||||||
|
github.com/jalaali/go-jalaali v0.0.0-20250521085720-bf793ab67800/go.mod h1:Wqfu7mjUHj9WDzSSPI5KfBclTTEnLveRUFr/ujWnTgE=
|
||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
|
|
@ -225,8 +234,6 @@ github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBF
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||||
github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM=
|
|
||||||
github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
|
@ -408,6 +415,18 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||||
|
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
|
@ -529,6 +548,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
|
@ -536,7 +557,6 @@ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRn
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
|
|
|
||||||
|
|
@ -1 +1,55 @@
|
||||||
package paymentapp
|
package paymentapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/gateway"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/repository"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/service"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/paymentapp/delivery/grpc"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/database"
|
||||||
|
sgrpc "git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
paymentrepo service.PaymentRepo
|
||||||
|
paymentMethodRepo service.PaymentMethodRepo
|
||||||
|
gwFactory service.GatewayFactory
|
||||||
|
paymentService *service.PaymentService
|
||||||
|
paymenthandler *grpc.Handler
|
||||||
|
rpcServer *sgrpc.RPCServer
|
||||||
|
server grpc.PaymentGrpcServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(ctx context.Context, cfg Config, conn *database.Database) (*App, error) {
|
||||||
|
paymentrepo := repository.NewPaymentRepository(conn.DB)
|
||||||
|
paymentMethodRepo := repository.NewPaymentMethodRepository(conn.DB)
|
||||||
|
gwFactory := gateway.NewGatewayFactory()
|
||||||
|
paymentService := service.NewPaymentService(paymentrepo, paymentMethodRepo, gwFactory)
|
||||||
|
|
||||||
|
paymenthandler := grpc.NewHandler(paymentService)
|
||||||
|
|
||||||
|
rpcServer := sgrpc.New(sgrpc.Config(cfg.Grpc))
|
||||||
|
|
||||||
|
server := grpc.NewPaymentGrpcServer(rpcServer, paymenthandler)
|
||||||
|
|
||||||
|
return &App{
|
||||||
|
paymentrepo: paymentrepo,
|
||||||
|
paymentMethodRepo: paymentMethodRepo,
|
||||||
|
gwFactory: gwFactory,
|
||||||
|
paymentService: paymentService,
|
||||||
|
paymenthandler: paymenthandler,
|
||||||
|
rpcServer: rpcServer,
|
||||||
|
server: server,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Start() error {
|
||||||
|
a.server.Serve()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) Stop(ctx context.Context) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,32 @@
|
||||||
package paymentapp
|
package paymentapp
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type GrpcConfig struct {
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
NetworkType string `yaml:"type"`
|
||||||
|
ShutDownCtxTimeout time.Duration `yaml:"shutdown_context_timeout"`
|
||||||
|
}
|
||||||
|
type PostgresConfig struct {
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Driver string `yaml:"driver"`
|
||||||
|
User string `yaml:"user"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
DbName string `yaml:"dbName"`
|
||||||
|
SSLMode string `yaml:"sslMode"`
|
||||||
|
MaxIdleConns int `yaml:"maxIdleConns"`
|
||||||
|
MaxOpenConns int `yaml:"maxOpenConns"`
|
||||||
|
ConnMaxLifetime int `yaml:"connMaxLifetime"`
|
||||||
|
PathOfMigrations string `yaml:"pathOfMigrations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoggerConfig struct {
|
||||||
|
Level string `yaml:"level" json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Grpc GrpcConfig `yaml:"grpc" json:"grpc"`
|
||||||
|
Postgres PostgresConfig `yaml:"postgres" json:"postgres"`
|
||||||
|
Logger LoggerConfig `yaml:"logger" json:"logger"`
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/domain/payment/service"
|
||||||
|
paymentpb "git.gocasts.ir/ebhomengo/niki/paymentapp/protobuf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
paymentpb.UnimplementedPaymentServiceServer
|
||||||
|
scvPayment *service.PaymentService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(s *service.PaymentService) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
scvPayment: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) InitiatePayment(ctx context.Context, req *paymentpb.InitiatePaymentRequest) (*paymentpb.InitiatePaymentResponse, error) {
|
||||||
|
|
||||||
|
paymentResponse, err := h.scvPayment.InitiatePayment(ctx, service.InitiatePaymentRequest{
|
||||||
|
UserID: uint(req.GetUserId()),
|
||||||
|
PayableType: entity.PayableType(req.GetPayableType()),
|
||||||
|
PayableID: uint(req.GetPayableId()),
|
||||||
|
GatewayCode: req.GetGatewayCode(),
|
||||||
|
CallbackURL: req.GetCallbackUrl(),
|
||||||
|
Amount: req.GetAmount(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &paymentpb.InitiatePaymentResponse{
|
||||||
|
PaymentId: uint64(paymentResponse.PaymentID),
|
||||||
|
RedirectUrl: paymentResponse.RedirectURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
paymentpb "git.gocasts.ir/ebhomengo/niki/paymentapp/protobuf"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentGrpcServer struct {
|
||||||
|
server *grpc.RPCServer
|
||||||
|
handler *Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPaymentGrpcServer(server *grpc.RPCServer, handler *Handler) PaymentGrpcServer {
|
||||||
|
return PaymentGrpcServer{
|
||||||
|
server: server,
|
||||||
|
handler: handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PaymentGrpcServer) Serve() error {
|
||||||
|
listener, err := net.Listen(s.server.Config.NetworkType, fmt.Sprintf(":%d", s.server.Config.Port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
paymentpb.RegisterPaymentServiceServer(s.server.Server, s.handler)
|
||||||
|
if err := s.server.Server.Serve(listener); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PaymentGrpcServer) Stop() {
|
||||||
|
s.server.Stop()
|
||||||
|
}
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHandler(userService UserService) *Handler {
|
|
||||||
return &Handler{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handler) HealthCheck(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("OK"))
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
func (s *Server) registerRoutes() {
|
|
||||||
s.httpServer.GET("/health", s.handler.HealthCheck)
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
package http
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
httpServer *httpserver.Server
|
|
||||||
handler *Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer(httpServer *httpserver.Server, handler *Handler) *Server {
|
|
||||||
return &Server{
|
|
||||||
httpServer: httpServer,
|
|
||||||
handler: handler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Serve() error {
|
|
||||||
s.registerRoutes()
|
|
||||||
return s.httpServer.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
|
||||||
return s.httpServer.Stop()
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-go v1.36.11
|
||||||
|
// protoc v4.25.1
|
||||||
|
// source: paymentapp/protobuf/payment.proto
|
||||||
|
|
||||||
|
package paymentpb
|
||||||
|
|
||||||
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
sync "sync"
|
||||||
|
unsafe "unsafe"
|
||||||
|
|
||||||
|
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Verify that this generated code is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||||
|
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||||
|
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
type InitiatePaymentRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
UserId uint64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
|
||||||
|
PayableType string `protobuf:"bytes,2,opt,name=payable_type,json=payableType,proto3" json:"payable_type,omitempty"`
|
||||||
|
PayableId uint64 `protobuf:"varint,3,opt,name=payable_id,json=payableId,proto3" json:"payable_id,omitempty"`
|
||||||
|
GatewayCode string `protobuf:"bytes,4,opt,name=gateway_code,json=gatewayCode,proto3" json:"gateway_code,omitempty"`
|
||||||
|
CallbackUrl string `protobuf:"bytes,5,opt,name=callback_url,json=callbackUrl,proto3" json:"callback_url,omitempty"`
|
||||||
|
Amount int64 `protobuf:"varint,6,opt,name=amount,proto3" json:"amount,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) Reset() {
|
||||||
|
*x = InitiatePaymentRequest{}
|
||||||
|
mi := &file_paymentapp_protobuf_payment_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*InitiatePaymentRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_paymentapp_protobuf_payment_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use InitiatePaymentRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*InitiatePaymentRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_paymentapp_protobuf_payment_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) GetUserId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.UserId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) GetPayableType() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PayableType
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) GetPayableId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.PayableId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) GetGatewayCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.GatewayCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) GetCallbackUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.CallbackUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentRequest) GetAmount() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Amount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitiatePaymentResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
PaymentId uint64 `protobuf:"varint,1,opt,name=payment_id,json=paymentId,proto3" json:"payment_id,omitempty"`
|
||||||
|
RedirectUrl string `protobuf:"bytes,2,opt,name=redirect_url,json=redirectUrl,proto3" json:"redirect_url,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentResponse) Reset() {
|
||||||
|
*x = InitiatePaymentResponse{}
|
||||||
|
mi := &file_paymentapp_protobuf_payment_proto_msgTypes[1]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*InitiatePaymentResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_paymentapp_protobuf_payment_proto_msgTypes[1]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use InitiatePaymentResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*InitiatePaymentResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_paymentapp_protobuf_payment_proto_rawDescGZIP(), []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentResponse) GetPaymentId() uint64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.PaymentId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *InitiatePaymentResponse) GetRedirectUrl() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.RedirectUrl
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var File_paymentapp_protobuf_payment_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
|
const file_paymentapp_protobuf_payment_proto_rawDesc = "" +
|
||||||
|
"\n" +
|
||||||
|
"!paymentapp/protobuf/payment.proto\x12\apayment\"\xd1\x01\n" +
|
||||||
|
"\x16InitiatePaymentRequest\x12\x17\n" +
|
||||||
|
"\auser_id\x18\x01 \x01(\x04R\x06userId\x12!\n" +
|
||||||
|
"\fpayable_type\x18\x02 \x01(\tR\vpayableType\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"payable_id\x18\x03 \x01(\x04R\tpayableId\x12!\n" +
|
||||||
|
"\fgateway_code\x18\x04 \x01(\tR\vgatewayCode\x12!\n" +
|
||||||
|
"\fcallback_url\x18\x05 \x01(\tR\vcallbackUrl\x12\x16\n" +
|
||||||
|
"\x06amount\x18\x06 \x01(\x03R\x06amount\"[\n" +
|
||||||
|
"\x17InitiatePaymentResponse\x12\x1d\n" +
|
||||||
|
"\n" +
|
||||||
|
"payment_id\x18\x01 \x01(\x04R\tpaymentId\x12!\n" +
|
||||||
|
"\fredirect_url\x18\x02 \x01(\tR\vredirectUrl2f\n" +
|
||||||
|
"\x0ePaymentService\x12T\n" +
|
||||||
|
"\x0fInitiatePayment\x12\x1f.payment.InitiatePaymentRequest\x1a .payment.InitiatePaymentResponseB=Z;git.gocasts.ir/ebhomengo/niki/paymentapp/protobuf;paymentpbb\x06proto3"
|
||||||
|
|
||||||
|
var (
|
||||||
|
file_paymentapp_protobuf_payment_proto_rawDescOnce sync.Once
|
||||||
|
file_paymentapp_protobuf_payment_proto_rawDescData []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func file_paymentapp_protobuf_payment_proto_rawDescGZIP() []byte {
|
||||||
|
file_paymentapp_protobuf_payment_proto_rawDescOnce.Do(func() {
|
||||||
|
file_paymentapp_protobuf_payment_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_paymentapp_protobuf_payment_proto_rawDesc), len(file_paymentapp_protobuf_payment_proto_rawDesc)))
|
||||||
|
})
|
||||||
|
return file_paymentapp_protobuf_payment_proto_rawDescData
|
||||||
|
}
|
||||||
|
|
||||||
|
var file_paymentapp_protobuf_payment_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||||
|
var file_paymentapp_protobuf_payment_proto_goTypes = []any{
|
||||||
|
(*InitiatePaymentRequest)(nil), // 0: payment.InitiatePaymentRequest
|
||||||
|
(*InitiatePaymentResponse)(nil), // 1: payment.InitiatePaymentResponse
|
||||||
|
}
|
||||||
|
var file_paymentapp_protobuf_payment_proto_depIdxs = []int32{
|
||||||
|
0, // 0: payment.PaymentService.InitiatePayment:input_type -> payment.InitiatePaymentRequest
|
||||||
|
1, // 1: payment.PaymentService.InitiatePayment:output_type -> payment.InitiatePaymentResponse
|
||||||
|
1, // [1:2] is the sub-list for method output_type
|
||||||
|
0, // [0:1] is the sub-list for method input_type
|
||||||
|
0, // [0:0] is the sub-list for extension type_name
|
||||||
|
0, // [0:0] is the sub-list for extension extendee
|
||||||
|
0, // [0:0] is the sub-list for field type_name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { file_paymentapp_protobuf_payment_proto_init() }
|
||||||
|
func file_paymentapp_protobuf_payment_proto_init() {
|
||||||
|
if File_paymentapp_protobuf_payment_proto != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
type x struct{}
|
||||||
|
out := protoimpl.TypeBuilder{
|
||||||
|
File: protoimpl.DescBuilder{
|
||||||
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_paymentapp_protobuf_payment_proto_rawDesc), len(file_paymentapp_protobuf_payment_proto_rawDesc)),
|
||||||
|
NumEnums: 0,
|
||||||
|
NumMessages: 2,
|
||||||
|
NumExtensions: 0,
|
||||||
|
NumServices: 1,
|
||||||
|
},
|
||||||
|
GoTypes: file_paymentapp_protobuf_payment_proto_goTypes,
|
||||||
|
DependencyIndexes: file_paymentapp_protobuf_payment_proto_depIdxs,
|
||||||
|
MessageInfos: file_paymentapp_protobuf_payment_proto_msgTypes,
|
||||||
|
}.Build()
|
||||||
|
File_paymentapp_protobuf_payment_proto = out.File
|
||||||
|
file_paymentapp_protobuf_payment_proto_goTypes = nil
|
||||||
|
file_paymentapp_protobuf_payment_proto_depIdxs = nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package payment;
|
||||||
|
option go_package = "git.gocasts.ir/ebhomengo/niki/paymentapp/protobuf;paymentpb";
|
||||||
|
|
||||||
|
service PaymentService {
|
||||||
|
rpc InitiatePayment (InitiatePaymentRequest) returns (InitiatePaymentResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
message InitiatePaymentRequest {
|
||||||
|
uint64 user_id = 1;
|
||||||
|
string payable_type = 2;
|
||||||
|
uint64 payable_id = 3;
|
||||||
|
string gateway_code = 4;
|
||||||
|
string callback_url = 5;
|
||||||
|
int64 amount = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message InitiatePaymentResponse {
|
||||||
|
uint64 payment_id = 1;
|
||||||
|
string redirect_url = 2;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.1
|
||||||
|
// - protoc v4.25.1
|
||||||
|
// source: paymentapp/protobuf/payment.proto
|
||||||
|
|
||||||
|
package paymentpb
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
PaymentService_InitiatePayment_FullMethodName = "/payment.PaymentService/InitiatePayment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PaymentServiceClient is the client API for PaymentService service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type PaymentServiceClient interface {
|
||||||
|
InitiatePayment(ctx context.Context, in *InitiatePaymentRequest, opts ...grpc.CallOption) (*InitiatePaymentResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type paymentServiceClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPaymentServiceClient(cc grpc.ClientConnInterface) PaymentServiceClient {
|
||||||
|
return &paymentServiceClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *paymentServiceClient) InitiatePayment(ctx context.Context, in *InitiatePaymentRequest, opts ...grpc.CallOption) (*InitiatePaymentResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(InitiatePaymentResponse)
|
||||||
|
err := c.cc.Invoke(ctx, PaymentService_InitiatePayment_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaymentServiceServer is the server API for PaymentService service.
|
||||||
|
// All implementations must embed UnimplementedPaymentServiceServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type PaymentServiceServer interface {
|
||||||
|
InitiatePayment(context.Context, *InitiatePaymentRequest) (*InitiatePaymentResponse, error)
|
||||||
|
mustEmbedUnimplementedPaymentServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedPaymentServiceServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedPaymentServiceServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedPaymentServiceServer) InitiatePayment(context.Context, *InitiatePaymentRequest) (*InitiatePaymentResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method InitiatePayment not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedPaymentServiceServer) mustEmbedUnimplementedPaymentServiceServer() {}
|
||||||
|
func (UnimplementedPaymentServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafePaymentServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to PaymentServiceServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafePaymentServiceServer interface {
|
||||||
|
mustEmbedUnimplementedPaymentServiceServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterPaymentServiceServer(s grpc.ServiceRegistrar, srv PaymentServiceServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedPaymentServiceServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&PaymentService_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _PaymentService_InitiatePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(InitiatePaymentRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(PaymentServiceServer).InitiatePayment(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: PaymentService_InitiatePayment_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(PaymentServiceServer).InitiatePayment(ctx, req.(*InitiatePaymentRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaymentService_ServiceDesc is the grpc.ServiceDesc for PaymentService service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var PaymentService_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "payment.PaymentService",
|
||||||
|
HandlerType: (*PaymentServiceServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "InitiatePayment",
|
||||||
|
Handler: _PaymentService_InitiatePayment_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{},
|
||||||
|
Metadata: "paymentapp/protobuf/payment.proto",
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Driver string `koanf:"driver"`
|
||||||
|
Host string `koanf:"host"`
|
||||||
|
Port int `koanf:"port"`
|
||||||
|
Username string `koanf:"user"`
|
||||||
|
Password string `koanf:"password"`
|
||||||
|
DBName string `koanf:"db_name"`
|
||||||
|
SSLMode string `koanf:"ssl_mode"`
|
||||||
|
MaxIdleConns int `koanf:"max_idle_conns"`
|
||||||
|
MaxOpenConns int `koanf:"max_open_conns"`
|
||||||
|
ConnMaxLifetime int `koanf:"conn_max_lifetime"`
|
||||||
|
PathOfMigrations string `koanf:"path_of_migrations"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DSNBuilder interface {
|
||||||
|
BuildDSN(config Config) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
DB *sql.DB
|
||||||
|
Dialect string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Connect(config Config) (*Database, error) {
|
||||||
|
|
||||||
|
dsnB, DSNErr := GetDSNBuilder(config.Driver)
|
||||||
|
if DSNErr != nil {
|
||||||
|
return nil, DSNErr
|
||||||
|
}
|
||||||
|
dsn := dsnB.BuildDSN(config)
|
||||||
|
conn, err := sql.Open(config.Driver, dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.Ping(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to connect to database: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetMaxIdleConns(config.MaxIdleConns)
|
||||||
|
conn.SetMaxOpenConns(config.MaxOpenConns)
|
||||||
|
conn.SetConnMaxLifetime(time.Duration(config.ConnMaxLifetime) * time.Second)
|
||||||
|
|
||||||
|
fmt.Println("Database connection established successfully")
|
||||||
|
|
||||||
|
return &Database{DB: conn, Dialect: config.Driver}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close(conn *sql.DB) error {
|
||||||
|
return conn.Close()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql" // MySQL
|
||||||
|
_ "github.com/lib/pq" // PostgreSQL
|
||||||
|
_ "github.com/mattn/go-sqlite3" // SQLite
|
||||||
|
)
|
||||||
|
|
||||||
|
var DSNRegistry = make(map[string]DSNBuilder)
|
||||||
|
|
||||||
|
func RegisterDSNBuilder(dsn string, builder DSNBuilder) {
|
||||||
|
DSNRegistry[dsn] = builder
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDSNBuilder(dsn string) (DSNBuilder, error) {
|
||||||
|
builder, exists := DSNRegistry[dsn]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("database driver %s is not registered", dsn)
|
||||||
|
}
|
||||||
|
return builder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterDSNBuilder("postgres", &PostgresDSNBuilder{})
|
||||||
|
RegisterDSNBuilder("mysql", &MySQLDSNBuilder{})
|
||||||
|
RegisterDSNBuilder("sqlite", &SQLiteDSNBuilder{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostgresDSNBuilder struct{}
|
||||||
|
|
||||||
|
func (p *PostgresDSNBuilder) BuildDSN(config Config) string {
|
||||||
|
return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||||
|
config.Host, config.Port, config.Username, config.Password, config.DBName, config.SSLMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MySQLDSNBuilder struct{}
|
||||||
|
|
||||||
|
func (m *MySQLDSNBuilder) BuildDSN(config Config) string {
|
||||||
|
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%s",
|
||||||
|
config.Username, config.Password, config.Host, config.Port, config.DBName, config.SSLMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SQLiteDSNBuilder struct{}
|
||||||
|
|
||||||
|
func (s *SQLiteDSNBuilder) BuildDSN(config Config) string {
|
||||||
|
return config.DBName
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
querier "git.gocasts.ir/ebhomengo/niki/pkg/query_transaction/sql"
|
||||||
|
_ "github.com/jackc/pgx/v5/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Host string `koanf:"host"`
|
||||||
|
Port int `koanf:"port"`
|
||||||
|
User string `koanf:"user"`
|
||||||
|
Password string `koanf:"password"`
|
||||||
|
DbName string `koanf:"dbName"`
|
||||||
|
SSLMode string `koanf:"sslMode"`
|
||||||
|
MaxIdleConn int `koanf:"maxIdleConns"`
|
||||||
|
MaxOpenConn int `koanf:"maxOpenConns"`
|
||||||
|
ConnMaxLifetime int `koanf:"connMaxLifetime"`
|
||||||
|
PathOfMigrations string `koanf:"pathOfMigrations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
config Config
|
||||||
|
db *querier.SQLDB
|
||||||
|
mu sync.Mutex
|
||||||
|
statements map[statementKey]*sql.Stmt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) Conn() *querier.SQLDB {
|
||||||
|
return db.db
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config Config) *DB {
|
||||||
|
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||||
|
config.Host, config.Port, config.User, config.Password, config.DbName, config.SSLMode)
|
||||||
|
|
||||||
|
db, err := sql.Open("pgx", dsn)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("can't open postgres db: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIdle := config.MaxIdleConn
|
||||||
|
|
||||||
|
maxOpen := config.MaxOpenConn
|
||||||
|
|
||||||
|
lifetime := time.Duration(config.ConnMaxLifetime) * time.Second
|
||||||
|
|
||||||
|
db.SetMaxIdleConns(maxIdle)
|
||||||
|
db.SetMaxOpenConns(maxOpen)
|
||||||
|
db.SetConnMaxLifetime(lifetime)
|
||||||
|
|
||||||
|
return &DB{
|
||||||
|
config: config,
|
||||||
|
db: &querier.SQLDB{DB: db},
|
||||||
|
statements: make(map[statementKey]*sql.Stmt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) PrepareStatement(ctx context.Context, key statementKey, query string) (*sql.Stmt, error) {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
if stmt, ok := db.statements[key]; ok {
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := db.db.PrepareContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("prepare statement %q: %w", key, err)
|
||||||
|
}
|
||||||
|
db.statements[key] = stmt
|
||||||
|
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) CloseStatements() error {
|
||||||
|
db.mu.Lock()
|
||||||
|
defer db.mu.Unlock()
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
for key, stmt := range db.statements {
|
||||||
|
if err := stmt.Close(); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
delete(db.statements, key)
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DB) Close() error {
|
||||||
|
return db.db.DB.Close()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package migrator
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package postgres
|
||||||
|
|
||||||
|
type statementKey uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatementKeyAWalletGetTransactionHistory statementKey = iota + 1
|
||||||
|
StatementKeyWalletInsertTransaction
|
||||||
|
StatementKeyWalletGetUserWallet
|
||||||
|
)
|
||||||
|
|
@ -1,6 +1,23 @@
|
||||||
package errmsg
|
package errmsg
|
||||||
|
|
||||||
|
type ErrorResponse struct {
|
||||||
|
Message string `json:"message"` // General error message
|
||||||
|
Errors map[string]interface{} `json:"errors,omitempty"` // Additional detail of error
|
||||||
|
InternalErrCode string `json:"internal_error_code,omitempty"` // Custom error code (optional)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrorResponse) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ErrValidationFailed = "input validation failed"
|
||||||
|
ErrUnexpectedError = "unexpected error occurred"
|
||||||
|
ErrInvalidRequestFormat = "invalid request format"
|
||||||
|
ErrGetUserInfo = "get user info failed"
|
||||||
|
ErrFailedDecodeBase64 = "decode data to base 64 failed"
|
||||||
|
ErrFailedUnmarshalJson = "unmarshal data to JSON failed"
|
||||||
|
ErrUnauthorized = "unauthorized"
|
||||||
ErrorMsgAdminNotAllowed = "admin is not allowed"
|
ErrorMsgAdminNotAllowed = "admin is not allowed"
|
||||||
ErrorMsgNotFound = "record not found"
|
ErrorMsgNotFound = "record not found"
|
||||||
ErrorMsgSomethingWentWrong = "something went wrong"
|
ErrorMsgSomethingWentWrong = "something went wrong"
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Client struct {
|
||||||
Host string `koanf:"host"`
|
Host string `koanf:"host"`
|
||||||
Port int `koanf:"port"`
|
Port int `koanf:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(cfg Config) (*grpc.ClientConn, error) {
|
func NewClient(cfg Client) (*grpc.ClientConn, error) {
|
||||||
address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||||
grpcConn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
conn, err := grpc.DialContext(context.Background(), address, grpc.WithInsecure())
|
||||||
|
|
||||||
|
fmt.Println(err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to connect to gRPC server: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return grpcConn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
package grpc
|
package grpc
|
||||||
|
|
||||||
import "google.golang.org/grpc"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Port int `koanf:"port"`
|
||||||
|
NetworkType string `koanf:"type"`
|
||||||
|
ShutDownCtxTimeout time.Duration `koanf:"shutdown_context_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
type RPCServer struct {
|
type RPCServer struct {
|
||||||
|
Config Config
|
||||||
Server *grpc.Server
|
Server *grpc.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() RPCServer {
|
func New(cfg Config) *RPCServer {
|
||||||
return RPCServer{
|
grpcServer := grpc.NewServer()
|
||||||
Server: grpc.NewServer(),
|
|
||||||
|
return &RPCServer{
|
||||||
|
Server: grpcServer,
|
||||||
|
Config: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
package http_server
|
package httpserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port int `koanf:"port"`
|
Port int `koanf:"port"`
|
||||||
|
Cors Cors `koanf:"cors"`
|
||||||
|
ShutDownCtxTimeout time.Duration `koanf:"shutdown_context_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cors struct {
|
||||||
|
AllowOrigins []string `koanf:"allow_origins"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
|
@ -16,10 +24,15 @@ type Server struct {
|
||||||
Config Config
|
Config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(cfg Config) Server {
|
func New(cfg Config) Server {
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
|
|
||||||
e.Use(middleware.RequestLogger())
|
e.Use(middleware.RequestID())
|
||||||
|
|
||||||
|
e.Use(middleware.Logger())
|
||||||
|
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||||
|
AllowOrigins: cfg.Cors.AllowOrigins,
|
||||||
|
}))
|
||||||
e.Use(middleware.Recover())
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
return Server{
|
return Server{
|
||||||
|
|
@ -28,6 +41,16 @@ func NewServer(cfg Config) Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// register custom handler
|
||||||
|
func (s Server) RegisterHandler(route string, handler echo.HandlerFunc) {
|
||||||
|
s.Router.GET(route, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start server
|
||||||
func (s Server) Start() error {
|
func (s Server) Start() error {
|
||||||
return s.Router.Start(fmt.Sprintf(":%d", s.Config.Port))
|
return s.Router.Start(fmt.Sprintf(":%d", s.Config.Port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Server) Stop(ctx context.Context) error {
|
||||||
|
return s.Router.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,69 @@
|
||||||
package productapp
|
package productapp
|
||||||
|
|
||||||
import "net/http"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/productapp/service/product"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
|
||||||
|
producthttp "git.gocasts.ir/ebhomengo/niki/productapp/delivery/http"
|
||||||
|
productdb "git.gocasts.ir/ebhomengo/niki/productapp/repository/database"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPServerConfig struct {
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
HTTPServer HTTPServerConfig
|
||||||
|
Database mysql.Config
|
||||||
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Config Config
|
Config Config
|
||||||
HTTPServer *http.Server
|
HTTPServer *producthttp.Server
|
||||||
|
productSvc product.Service
|
||||||
|
DB *productdb.DB
|
||||||
|
Echo *echo.Echo
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(cfg Config) *Application {
|
||||||
|
db := mysql.New(cfg.Database)
|
||||||
|
productDB := productdb.New(db)
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
e.Use(middleware.Recover())
|
||||||
|
|
||||||
|
hsrv := &httpserver.Server{
|
||||||
|
Router: e,
|
||||||
|
}
|
||||||
|
|
||||||
|
productSvc := product.New(productDB)
|
||||||
|
|
||||||
|
srv := producthttp.NewServer(hsrv, productSvc)
|
||||||
|
srv.RegisterRoutes()
|
||||||
|
|
||||||
|
return &Application{
|
||||||
|
Config: cfg,
|
||||||
|
productSvc: productSvc,
|
||||||
|
HTTPServer: srv,
|
||||||
|
DB: productDB,
|
||||||
|
Echo: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) Start() {
|
||||||
|
address := fmt.Sprintf(":%d", app.Config.HTTPServer.Port)
|
||||||
|
log.Printf("Product Service listening on %s", address)
|
||||||
|
if err := app.Echo.Start(address); err != nil {
|
||||||
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *Application) Stop(ctx context.Context) error {
|
||||||
|
return app.Echo.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1 @@
|
||||||
package productapp
|
package productapp
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,38 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/productapp/service/product"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct{}
|
type Handler struct {
|
||||||
|
productService product.Service
|
||||||
func NewHandler() *Handler {
|
Logger *slog.Logger
|
||||||
return &Handler{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Handler) HealthCheck(c echo.Context) error {
|
func NewHandler(productService product.Service) *Handler {
|
||||||
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
return &Handler{
|
||||||
|
productService: productService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) getProductList(c echo.Context) error {
|
||||||
|
var req product.GetProductListRequest
|
||||||
|
|
||||||
|
if err := c.Bind(&req); err != nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, errmsg.ErrorResponse{
|
||||||
|
Message: errmsg.ErrInvalidRequestFormat,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.productService.GetProducts(c.Request().Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
// todo handle validation error
|
||||||
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h Handler) HealthCheck(c echo.Context) error {
|
||||||
|
return c.JSON(http.StatusOK, map[string]string{"status": "healthy"})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue