forked from ebhomengo/niki
Merge branch 'develop' into fix
This commit is contained in:
commit
e58c70a8f2
|
|
@ -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
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
#vscode
|
||||
.vscode
|
||||
|
||||
.DS_Store
|
||||
# Binaries for programs and plugins
|
||||
niki
|
||||
*.exe
|
||||
|
|
@ -29,4 +29,6 @@ tmp
|
|||
logs/
|
||||
mise.log
|
||||
|
||||
curl
|
||||
curl
|
||||
|
||||
cmd/**/temp/main
|
||||
|
|
@ -19,7 +19,7 @@ FROM alpine:3.20 AS runtime
|
|||
# Copy the binary from the builder stage
|
||||
COPY --from=builder /niki/niki .
|
||||
|
||||
# Copy migration files
|
||||
# Copy migrations files
|
||||
COPY --from=builder /niki/repository/mysql/migration ./repository/mysql/migration
|
||||
|
||||
# Expose application port
|
||||
|
|
|
|||
126
Makefile
126
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
|
||||
|
||||
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)/...
|
||||
start: build
|
||||
$(BUILD_DIR)/$(BINARY_NAME)
|
||||
|
||||
test:
|
||||
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:
|
||||
@which gofumpt || (go install mvdan.cc/gofumpt@latest)
|
||||
@gofumpt -l -w $(ROOT)
|
||||
@gofumpt -l -w .
|
||||
@which gci || (go install github.com/daixiang0/gci@latest)
|
||||
@gci write $(ROOT) --skip-generated --skip-vendor
|
||||
@which golangci-lint || (go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.0)
|
||||
@gci write . --skip-generated --skip-vendor
|
||||
@golangci-lint run --fix
|
||||
|
||||
build:
|
||||
go build -o niki main.go
|
||||
|
||||
run:
|
||||
go run main.go --migrate
|
||||
|
||||
docker:
|
||||
sudo docker compose up -d
|
||||
|
||||
swagger:
|
||||
swag init
|
||||
|
||||
# Live Reload
|
||||
watch:
|
||||
@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 \
|
||||
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 \
|
||||
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 \
|
||||
echo "You chose not to install CompileDaemon. Exiting..."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
fi
|
||||
|
||||
# ====================================================================================
|
||||
# Database Migration Commands (legacy niki-core)
|
||||
# ====================================================================================
|
||||
.PHONY: migrate/status migrate/new migrate/up migrate/down
|
||||
|
||||
migrate/status:
|
||||
@sql-migrate status -env="production" -config=repository/mysql/dbconfig.yml
|
||||
|
||||
|
|
@ -63,4 +73,68 @@ migrate/up: confirm
|
|||
|
||||
migrate/down: confirm
|
||||
@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,44 @@
|
|||
package accountapp
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/accountapp/delivery/grpc"
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/kavenegar"
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/account/repository/mysql"
|
||||
redisRepo "git.gocasts.ir/ebhomengo/niki/domain/account/repository/redis"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/account/service"
|
||||
database "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
rpcPkg "git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
GrpcServer grpc.Server
|
||||
Config Config
|
||||
accountSvc service.Service
|
||||
}
|
||||
|
||||
func Setup(cfg Config, db *database.DB) Application {
|
||||
redisConn := redis.New(cfg.Redis)
|
||||
otpRepo := redisRepo.NewRepositoryOtp(redisConn)
|
||||
mysqlRepo := mysql.New(db)
|
||||
smsAdapter := kavenegar.New(cfg.Kavenegar)
|
||||
accountSvc := service.NewService(cfg.accountSvc, otpRepo, mysqlRepo, smsAdapter)
|
||||
|
||||
rpcServer := rpcPkg.New(cfg.grpcServerCfg)
|
||||
|
||||
return Application{
|
||||
accountSvc: accountSvc,
|
||||
Config: cfg,
|
||||
GrpcServer: grpc.New(rpcServer, accountSvc),
|
||||
}
|
||||
}
|
||||
|
||||
func (app *Application) Start() {
|
||||
err := app.GrpcServer.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("error in serving GRPC server: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package accountapp
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/kavenegar"
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/account/service"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
accountSvc service.Config `koanf:"service"`
|
||||
Redis redis.Config `koanf:"redis_db"`
|
||||
MysqlDB mysql.Config `koanf:"mysql_db"`
|
||||
Kavenegar kavenegar.Config `koanf:"kavenegar"`
|
||||
grpcServerCfg grpc.Config `koanf:"grpc_server"`
|
||||
grpcClientCfg grpc.Client `koanf:"grpc_client"`
|
||||
PathOfMigration string `koanf:"path_of_migration"`
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
pb "git.gocasts.ir/ebhomengo/niki/contract/goprotobuf/account"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/account/service"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/grpc"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
server *grpc.RPCServer
|
||||
accountSvc service.Service
|
||||
pb.UnimplementedAccountServiceServer
|
||||
}
|
||||
|
||||
func New(server *grpc.RPCServer, accountSvc service.Service) Server {
|
||||
return Server{
|
||||
server: server,
|
||||
accountSvc: accountSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Server) SendOtp(ctx context.Context, req *pb.SendOtpRequest) (*pb.SendOtpResponse, error) {
|
||||
err := s.accountSvc.SendOTP(ctx, req.PhoneNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.SendOtpResponse{}, nil
|
||||
}
|
||||
|
||||
func (s Server) LoginOrRegister(ctx context.Context, req *pb.LoginOrRegisterRequest) (*pb.LoginOrRegisterResponse, error) {
|
||||
res := &pb.LoginOrRegisterResponse{}
|
||||
driver, err := s.accountSvc.LoginOrRegisterDriver(ctx, req.PhoneNumber, req.VerifyCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := uint64(driver.ID)
|
||||
|
||||
res.Id = id
|
||||
res.PhoneNumber = driver.PhoneNumber
|
||||
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
func (s Server) Start() error {
|
||||
listener, err := net.Listen(s.server.Config.NetworkType, fmt.Sprintf(":%d", s.server.Config.Port))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accountSvcServer := Server{}
|
||||
|
||||
pb.RegisterAccountServiceServer(s.server.Server, &accountSvcServer)
|
||||
|
||||
if err := s.server.Server.Serve(listener); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package account
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pb "git.gocasts.ir/ebhomengo/niki/contract/goprotobuf/account"
|
||||
"git.gocasts.ir/ebhomengo/niki/driverapp/service"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/types"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Conn *grpc.ClientConn
|
||||
}
|
||||
|
||||
func New(conn *grpc.ClientConn) *Client {
|
||||
return &Client{
|
||||
Conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Client) SendOTP(ctx context.Context, phoneNumber string) error {
|
||||
|
||||
client := pb.NewAccountServiceClient(c.Conn)
|
||||
|
||||
_, err := client.SendOtp(ctx, &pb.SendOtpRequest{
|
||||
PhoneNumber: phoneNumber,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Client) LoginOrRegister(ctx context.Context, req service.LoginOrRegisterRequest) (service.LoginOrRegisterResponse, error) {
|
||||
|
||||
client := pb.NewAccountServiceClient(c.Conn)
|
||||
|
||||
res, err := client.LoginOrRegister(ctx, &pb.LoginOrRegisterRequest{
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
VerifyCode: req.VerifyCode,
|
||||
})
|
||||
if err != nil {
|
||||
return service.LoginOrRegisterResponse{}, err
|
||||
}
|
||||
|
||||
return service.LoginOrRegisterResponse{
|
||||
ID: types.ID(res.Id),
|
||||
PhoneNumber: res.PhoneNumber,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package authapp
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Application struct {
|
||||
Config Config
|
||||
HTTPServer *http.Server
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package authapp
|
||||
|
||||
type Config struct {
|
||||
// HTTP server config
|
||||
|
||||
// Database config
|
||||
|
||||
// Logger config
|
||||
|
||||
// Service config
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Handler struct{}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{}
|
||||
}
|
||||
|
||||
func (h Handler) HealthCheck(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package http
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package http
|
||||
|
||||
import httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
|
||||
|
||||
type Server struct {
|
||||
HTTPServer *httpserver.Server
|
||||
Handler *Handler
|
||||
}
|
||||
|
||||
func NewServer(httpserver *httpserver.Server) *Server {
|
||||
return &Server{
|
||||
HTTPServer: httpserver,
|
||||
Handler: NewHandler(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Serve() {
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {}
|
||||
|
||||
func (s *Server) RegisterRoutes() {}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package database
|
||||
|
||||
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
|
||||
type DB struct {
|
||||
conn *mysql.DB
|
||||
}
|
||||
|
||||
func New(conn *mysql.DB) *DB {
|
||||
return &DB{
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package service
|
||||
|
|
@ -0,0 +1 @@
|
|||
package service
|
||||
|
|
@ -0,0 +1 @@
|
|||
package service
|
||||
|
|
@ -0,0 +1 @@
|
|||
package service
|
||||
|
|
@ -1,8 +1,62 @@
|
|||
package benefactorapp
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"context"
|
||||
benefactorHttp "git.gocasts.ir/ebhomengo/niki/benefactorapp/delivery/http"
|
||||
repo "git.gocasts.ir/ebhomengo/niki/benefactorapp/repository/database"
|
||||
benefactor "git.gocasts.ir/ebhomengo/niki/benefactorapp/service"
|
||||
mySql "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
httpserver "git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
|
||||
logger "git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Config Config
|
||||
HTTPServer *http.Server
|
||||
Config Config
|
||||
HTTPServer benefactorHttp.Server
|
||||
BenefactorService benefactor.Service
|
||||
BenefactorHandler benefactorHttp.Handler
|
||||
BenefactorRepo benefactor.Repository
|
||||
DBConn mySql.DB
|
||||
}
|
||||
|
||||
func Setup(ctx context.Context, config Config, DB mySql.DB) *Application {
|
||||
log := logger.L()
|
||||
log.Info("logger starting ...")
|
||||
|
||||
db := mySql.New(config.MySQLDB)
|
||||
defer func() {
|
||||
if err := db.CloseStatements(); err != nil {
|
||||
log.Info("Error closing statements: %v\n", err)
|
||||
}
|
||||
}()
|
||||
log.Info("mysql connection starting ...")
|
||||
|
||||
// Initialize repositories
|
||||
benefactorRepo := repo.New(db)
|
||||
benefactorValidator := benefactor.NewValidator(benefactorRepo)
|
||||
|
||||
benefactorSvc := benefactor.NewService(benefactorRepo, benefactorValidator)
|
||||
benefactorHandler := benefactorHttp.NewHandler(benefactorSvc)
|
||||
|
||||
hServer, hErr := httpserver.New(config.HTTPServer)
|
||||
if hErr != nil {
|
||||
log.Error("Http Server error: %v,\n", hErr)
|
||||
}
|
||||
httpServer := benefactorHttp.NewServer(*hServer, *benefactorHandler)
|
||||
|
||||
return &Application{
|
||||
Config: config,
|
||||
HTTPServer: httpServer,
|
||||
BenefactorService: benefactorSvc,
|
||||
BenefactorHandler: *benefactorHandler,
|
||||
BenefactorRepo: benefactorRepo,
|
||||
DBConn: DB,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (app *Application) Start() {
|
||||
log := logger.L()
|
||||
log.Info("app starting ...")
|
||||
// TODO implementaion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
package benefactorapp
|
||||
|
||||
import (
|
||||
database "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
httpserver "git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// HTTP server config
|
||||
HTTPServer httpserver.Config `koanf:"http_server"`
|
||||
|
||||
// Database config
|
||||
MySQLDB database.Config `koanf:"mariadb"`
|
||||
|
||||
// Logger config
|
||||
Logger logger.Config `koanf:"logger"`
|
||||
|
||||
// Service config
|
||||
|
||||
// Database migration
|
||||
PathOfMigration string `koanf:"path_of_migration"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
benefactor "git.gocasts.ir/ebhomengo/niki/benefactorapp/service"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Handler struct{}
|
||||
type Handler struct {
|
||||
BebefactorService benefactor.Service
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{}
|
||||
func NewHandler(bService benefactor.Service) *Handler {
|
||||
return &Handler{
|
||||
BebefactorService: bService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) HealthCheck(c echo.Context) error {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
package http
|
||||
|
||||
import httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
|
||||
import httpserver "git.gocasts.ir/ebhomengo/niki/pkg/httpserver"
|
||||
|
||||
type Server struct {
|
||||
HTTPServer *httpserver.Server
|
||||
Handler *Handler
|
||||
HTTPServer httpserver.Server
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
func NewServer(httpserver *httpserver.Server) *Server {
|
||||
return &Server{
|
||||
func NewServer(httpserver httpserver.Server, handler Handler) Server {
|
||||
return Server{
|
||||
HTTPServer: httpserver,
|
||||
Handler: NewHandler(),
|
||||
Handler: handler,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package database
|
||||
|
||||
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
import "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
|
||||
type DB struct {
|
||||
conn *mysql.DB
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
-- +migrate Up
|
||||
-- please read this article to understand why we use VARCHAR(191)
|
||||
-- https://www.grouparoo.com/blog/varchar-191#why-varchar-and-not-text
|
||||
CREATE TABLE `benefactors1` (
|
||||
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`first_name` VARCHAR(191),
|
||||
`last_name` VARCHAR(191),
|
||||
`phone_number` VARCHAR(191) NOT NULL UNIQUE,
|
||||
`description` TEXT,
|
||||
`email` VARCHAR(191),
|
||||
`gender` ENUM('male','female'),
|
||||
`birth_date` TIMESTAMP,
|
||||
`status` ENUM('active','inactive') NOT NULL DEFAULT 'active',
|
||||
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `benefactors1`;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
-- +migrate Up
|
||||
CREATE TABLE `addresses1` (
|
||||
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`postal_code` VARCHAR(191) NOT NULL,
|
||||
`address` TEXT NOT NULL,
|
||||
`lat` FLOAT,
|
||||
`lon` FLOAT,
|
||||
`name` VARCHAR(191) NOT NULL,
|
||||
`city_id` INT NOT NULL,
|
||||
`province_id` INT NOT NULL,
|
||||
`benefactor_id` INT NOT NULL,
|
||||
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted_at` TIMESTAMP,
|
||||
FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`),
|
||||
FOREIGN KEY (`city_id`) REFERENCES `cities` (`id`),
|
||||
FOREIGN KEY (`benefactor_id`) REFERENCES `benefactors1` (`id`)
|
||||
);
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `addresses1`;
|
||||
|
|
@ -1 +1,17 @@
|
|||
package service
|
||||
|
||||
type Service struct {
|
||||
repository Repository
|
||||
validator Validator
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
//GetList(ctx context.Context, ID types.ID) ([]entity.Benefactor, error)
|
||||
}
|
||||
|
||||
func NewService(repo Repository, validator Validator) Service {
|
||||
return Service{
|
||||
repository: repo,
|
||||
validator: validator,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,11 @@
|
|||
package service
|
||||
|
||||
type ValidatorBenefactorRepository interface {
|
||||
}
|
||||
type Validator struct {
|
||||
repo ValidatorBenefactorRepository
|
||||
}
|
||||
|
||||
func NewValidator(repo ValidatorBenefactorRepository) Validator {
|
||||
return Validator{repo: repo}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package command
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var up bool
|
||||
var down bool
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run database migrations",
|
||||
Long: `This command runs the database migrations for the account service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migrate()
|
||||
},
|
||||
}
|
||||
|
||||
func migrate() {}
|
||||
|
||||
func init() {
|
||||
migrateCmd.Flags().BoolVar(&up, "up", false, "Run migrations up")
|
||||
migrateCmd.Flags().BoolVar(&down, "down", false, "Run migrations down")
|
||||
RootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package command
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "account_service",
|
||||
Short: "A CLI for account Service",
|
||||
Long: `account Service CLI is a tool to manage and run
|
||||
the account service, including migrations and server startup.`,
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package command
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "start a account service.",
|
||||
Long: `This command starts the main account service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
serve()
|
||||
},
|
||||
}
|
||||
|
||||
func serve() {}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package account
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/cmd/account/command"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.RootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
migrator "git.gocasts.ir/ebhomengo/niki/pkg/database/migrator"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var up bool
|
||||
var down bool
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run database migrations",
|
||||
Long: `This command runs the database migrations for the benefactor service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migrate()
|
||||
},
|
||||
}
|
||||
|
||||
func migrate() {
|
||||
var cfg = loadAppConfig()
|
||||
mysqlConfig := getMysqlConfig()
|
||||
|
||||
logger.Init(cfg.Logger)
|
||||
log := logger.L()
|
||||
|
||||
migratorCfg := migrator.Config{
|
||||
MysqlConfig: mysqlConfig,
|
||||
MigrationPath: cfg.PathOfMigration,
|
||||
MigrationDBName: "gorp_migrations",
|
||||
}
|
||||
// Run migrations if flags are set
|
||||
if migrateUp || migrateDown {
|
||||
mgr := migrator.New(migratorCfg)
|
||||
if migrateUp {
|
||||
log.Info("Running migrations up...")
|
||||
mgr.Up()
|
||||
log.Info("Migrations up completed.")
|
||||
}
|
||||
if migrateDown {
|
||||
log.Info("Running migrations down...")
|
||||
mgr.Down()
|
||||
log.Info("Migrations down completed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrateCmd.Flags().BoolVar(&up, "up", false, "Run migrations up")
|
||||
migrateCmd.Flags().BoolVar(&down, "down", false, "Run migrations down")
|
||||
RootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.gocasts.ir/ebhomengo/niki/benefactorapp"
|
||||
cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
database "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
serviceConfigMysql "git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/path"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "benefactor_service",
|
||||
Short: "A CLI for benefactor Service",
|
||||
Long: `benefactor Service CLI is a tool to manage and run
|
||||
the benefactor service, including migrations and server startup.`,
|
||||
}
|
||||
|
||||
func getMysqlConfig() mysql.Config {
|
||||
var cfg = loadAppConfig()
|
||||
|
||||
mysqlConfig := serviceConfigMysql.Config{
|
||||
Username: cfg.MySQLDB.Username,
|
||||
Password: cfg.MySQLDB.Password,
|
||||
Port: cfg.MySQLDB.Port,
|
||||
Host: cfg.MySQLDB.Host,
|
||||
DBName: cfg.MySQLDB.DBName,
|
||||
}
|
||||
|
||||
return mysqlConfig
|
||||
}
|
||||
|
||||
func getDB(cfg database.Config) *mysql.DB {
|
||||
|
||||
db := serviceConfigMysql.New(cfg)
|
||||
defer func() {
|
||||
if err := db.CloseStatements(); err != nil {
|
||||
fmt.Printf("Error closing statements: %v\n", err)
|
||||
}
|
||||
}()
|
||||
return db
|
||||
}
|
||||
func loadAppConfig() benefactorapp.Config {
|
||||
var cfg benefactorapp.Config
|
||||
|
||||
projectRoot, err := path.PathProjectRoot()
|
||||
if err != nil {
|
||||
log.Fatalf("Error finding project root: %v", err)
|
||||
}
|
||||
|
||||
yamlPath := os.Getenv("CONFIG_PATH")
|
||||
|
||||
if yamlPath == "" {
|
||||
defaultConfig := filepath.Join(projectRoot, "deploy", "benefactor", "development", "config.yml")
|
||||
if _, err := os.Stat(defaultConfig); err == nil {
|
||||
yamlPath = defaultConfig
|
||||
} else {
|
||||
yamlPath = filepath.Join(projectRoot, "deploy", "benefactor", "development", "config.local.yml")
|
||||
}
|
||||
}
|
||||
|
||||
options := cfgloader.Option{
|
||||
Prefix: "BENEFACTOR_",
|
||||
Delimiter: ".",
|
||||
Separator: "__",
|
||||
YamlFilePath: yamlPath,
|
||||
CallbackEnv: nil,
|
||||
}
|
||||
|
||||
if err := cfgloader.Load(options, &cfg); err != nil {
|
||||
log.Fatalf("Failed to load benefactor config: %v", err)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.gocasts.ir/ebhomengo/niki/benefactorapp"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var migrateUp bool
|
||||
var migrateDown bool
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start the benefactor service",
|
||||
Long: `This command starts the main benefactor service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
serve()
|
||||
},
|
||||
}
|
||||
|
||||
func serve() {
|
||||
var cfg = loadAppConfig()
|
||||
|
||||
// Initialize logger
|
||||
logger.Init(cfg.Logger)
|
||||
log := logger.L()
|
||||
|
||||
db := getDB(cfg.MySQLDB)
|
||||
migrate()
|
||||
|
||||
// Start the server
|
||||
log.Info("Starting benefactor Service...")
|
||||
|
||||
// Connect to the database
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
app := benefactorapp.Setup(ctx, cfg, *db)
|
||||
app.Start()
|
||||
}
|
||||
|
||||
func init() {
|
||||
serveCmd.Flags().BoolVar(&migrateUp, "migrate-up", false, "Run migrations up before starting the server")
|
||||
serveCmd.Flags().BoolVar(&migrateDown, "migrate-down", false, "Run migrations down before starting the server")
|
||||
RootCmd.AddCommand(serveCmd)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/cmd/benefactor/command"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.RootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package driverapp
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/driverapp"
|
||||
cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/migrator"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var cfg driverapp.Config
|
||||
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting current working directory: %v", err)
|
||||
}
|
||||
|
||||
options := cfgloader.Option{
|
||||
Prefix: "DRIVER_",
|
||||
Delimiter: ".",
|
||||
Separator: "__",
|
||||
YamlFilePath: filepath.Join(workingDir, "deploy", "driver", "development", "config.yaml"),
|
||||
CallbackEnv: nil,
|
||||
}
|
||||
|
||||
lErr := cfgloader.Load(options, &cfg)
|
||||
if lErr != nil {
|
||||
log.Fatalf("Failed to load driver config: %v", err)
|
||||
}
|
||||
|
||||
conn := mysql.New(cfg.MysqlDB)
|
||||
|
||||
mgr := migrator.New(cfg.MysqlDB, cfg.PathOfMigration)
|
||||
|
||||
migrate := flag.Bool("migrate", false, "perform database migrations")
|
||||
flag.Parse()
|
||||
|
||||
if *migrate {
|
||||
fmt.Println("Running migrations")
|
||||
mgr.Up()
|
||||
}
|
||||
|
||||
//dapp := driverapp.Setup(cfg)
|
||||
//dapp.Start()
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/migrator"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var up bool
|
||||
var down bool
|
||||
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run database migrations",
|
||||
Long: `This command runs the database migrations for the product service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migrate()
|
||||
},
|
||||
}
|
||||
|
||||
func migrate() {
|
||||
workingDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting working directory: %v", err)
|
||||
}
|
||||
|
||||
migrationPath := filepath.Join(workingDir, "productapp", "repository", "migrations")
|
||||
|
||||
// to run migrations when you want to run product service locally
|
||||
if path := os.Getenv("MIGRATION_PATH"); path != "" {
|
||||
migrationPath = path
|
||||
log.Printf("Using override migration path: %s", migrationPath)
|
||||
} else {
|
||||
log.Printf("Using default migration path: %s", migrationPath)
|
||||
}
|
||||
|
||||
// TODO: Load config from environment or config file
|
||||
mgr := migrator.New(migrator.Config{
|
||||
MysqlConfig: mysql.Config{
|
||||
Username: getEnv("DB_USERNAME", "root"),
|
||||
Password: getEnv("DB_PASSWORD", ""),
|
||||
Port: 3306,
|
||||
Host: getEnv("DB_HOST", "localhost"),
|
||||
DBName: getEnv("DB_NAME", "niki_db"),
|
||||
},
|
||||
MigrationPath: migrationPath,
|
||||
MigrationDBName: "product_migrations",
|
||||
})
|
||||
|
||||
if up {
|
||||
mgr.Up()
|
||||
} else if down {
|
||||
mgr.Down()
|
||||
} else {
|
||||
log.Println("Please specify a migration direction with --up or --down")
|
||||
}
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrateCmd.Flags().BoolVar(&up, "up", false, "Run migrations up")
|
||||
migrateCmd.Flags().BoolVar(&down, "down", false, "Run migrations down")
|
||||
RootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package command
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "product_service",
|
||||
Short: "A CLI for Product Service",
|
||||
Long: `Product Service CLI is a tool to manage and run
|
||||
the product service, including migrations and server startup.`,
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/productapp"
|
||||
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var port string
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start the product service",
|
||||
Long: `This command starts the main product service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
serve()
|
||||
},
|
||||
}
|
||||
|
||||
func serve() {
|
||||
log.Println("Product Service Starting...")
|
||||
|
||||
cfg := productapp.Config{
|
||||
HTTPServer: productapp.HTTPServerConfig{
|
||||
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)
|
||||
|
||||
go func() {
|
||||
app.Start()
|
||||
}()
|
||||
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
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() {
|
||||
serveCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to run the server on")
|
||||
RootCmd.AddCommand(serveCmd)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/cmd/productapp/command"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.RootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
package command
|
||||
|
|
@ -0,0 +1 @@
|
|||
package command
|
||||
|
|
@ -0,0 +1 @@
|
|||
package command
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
package command
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
cfgloader "git.gocasts.ir/ebhomengo/niki/pkg/cfg_loader"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/path"
|
||||
"git.gocasts.ir/ebhomengo/niki/shoppingbasketapp"
|
||||
"github.com/spf13/cobra"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "shoppingbasket_service",
|
||||
Short: "A CLI for shoppingbasket service",
|
||||
Long: `shoppingbasket Service CLI is a tool to manage and run
|
||||
the shoppingbasket service, including migrations and server startup.`,
|
||||
}
|
||||
|
||||
func loadAppConfig() shoppingbasketapp.Config {
|
||||
var cfg shoppingbasketapp.Config
|
||||
|
||||
projectRoot, err := path.PathProjectRoot()
|
||||
if err != nil {
|
||||
log.Fatalf("error finding project root: %v", err)
|
||||
}
|
||||
|
||||
yamlPath := os.Getenv("CONFIG_PATH")
|
||||
|
||||
if yamlPath == "" {
|
||||
defaultConfig := filepath.Join(projectRoot, "deploy", "shoppingbasket", "development", "config.yml")
|
||||
if _, err := os.Stat(defaultConfig); err == nil {
|
||||
yamlPath = defaultConfig
|
||||
} else {
|
||||
yamlPath = filepath.Join(projectRoot, "deploy", "shoppingbasket", "development", "config.local.yml")
|
||||
}
|
||||
}
|
||||
|
||||
options := cfgloader.Option{
|
||||
Prefix: "SHOPPINGBASKET_",
|
||||
Delimiter: ".",
|
||||
Separator: "__",
|
||||
YamlFilePath: yamlPath,
|
||||
CallbackEnv: nil,
|
||||
}
|
||||
|
||||
if err := cfgloader.Load(options, &cfg); err != nil {
|
||||
log.Fatalf("Failed to load benefactor config: %v", err)
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/logger"
|
||||
"git.gocasts.ir/ebhomengo/niki/shoppingbasketapp"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var ServeCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start shoppingbasket service",
|
||||
Long: `This command starts the main shoppingbasket service.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func serve() {
|
||||
var cfg = loadAppConfig()
|
||||
|
||||
logger.Init(cfg.Logger)
|
||||
l := logger.L()
|
||||
|
||||
l.Info("Starting shoppingbasket service...")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
app, err := shoppingbasketapp.Setup(ctx, cfg)
|
||||
if err != nil {
|
||||
l.Error("failed initialize shopping basket app", "error", err)
|
||||
log.Fatalf(fmt.Sprintf("error starting shopping basket app: %v", err))
|
||||
}
|
||||
|
||||
app.Start()
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(ServeCmd)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/cmd/shoppingbasketapp/command"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.RootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/staffapp/repository/database"
|
||||
"git.gocasts.ir/ebhomengo/niki/staffapp/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
staffDb := database.New()
|
||||
staffService := service.NewStaffService(staffDb)
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Staffapp OK!")
|
||||
})
|
||||
|
||||
http.HandleFunc("/staff", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var newStaff service.Staff
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
err := decoder.Decode(&newStaff)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request payload: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
createdStaff, err := staffService.RegisterStaff(newStaff.Name, newStaff.LastName, newStaff.PhoneNumber)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to register staff: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(createdStaff)
|
||||
})
|
||||
|
||||
http.HandleFunc("/staff/", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
idStr := r.URL.Path[len("/staff/"):]
|
||||
if idStr == "" {
|
||||
http.Error(w, "Missing staff ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid staff ID format", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
|
||||
st, err := staffService.Get(id)
|
||||
if err != nil {
|
||||
|
||||
if err.Error() == "staff not found" {
|
||||
http.Error(w, "Staff not found", http.StatusNotFound)
|
||||
} else {
|
||||
http.Error(w, "Failed to fetch staff: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(st)
|
||||
|
||||
case http.MethodPut:
|
||||
|
||||
var staffData struct {
|
||||
Name string `json:"Name"`
|
||||
LastName string `json:"LastName"`
|
||||
PhoneNumber string `json:"PhoneNumber"`
|
||||
}
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
if err := decoder.Decode(&staffData); err != nil {
|
||||
http.Error(w, "Invalid request payload: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
updatedStaff, err := staffService.Update(id, staffData.Name, staffData.LastName, staffData.PhoneNumber)
|
||||
if err != nil {
|
||||
|
||||
if err.Error() == "staff not found" {
|
||||
http.Error(w, "Staff not found", http.StatusNotFound)
|
||||
} else {
|
||||
http.Error(w, "Failed to update staff: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(updatedStaff)
|
||||
|
||||
case http.MethodDelete:
|
||||
|
||||
err = staffService.Remove(id)
|
||||
if err != nil {
|
||||
if err.Error() == "staff not found" {
|
||||
http.Error(w, "Staff not found", http.StatusNotFound)
|
||||
} else {
|
||||
http.Error(w, "Failed to remove staff: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
|
||||
default:
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/staffs", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := staffService.List()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to fetch staff list: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(list)
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
|
@ -12,12 +12,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultPrefix = "EB_"
|
||||
defaultDelimiter = "."
|
||||
defaultSeparator = "__"
|
||||
defaultYamlFilePath = "config.yml"
|
||||
defaultPrefix = "EB_"
|
||||
defaultDelimiter = "."
|
||||
defaultSeparator = "__"
|
||||
)
|
||||
|
||||
var defaultYamlFilePath = "config.yml"
|
||||
|
||||
var c Config
|
||||
|
||||
type Option struct {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,281 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v3.21.12
|
||||
// source: contract/protobuf/account/account.proto
|
||||
|
||||
package account
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
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 LoginOrRegisterRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PhoneNumber string `protobuf:"bytes,1,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"`
|
||||
VerifyCode string `protobuf:"bytes,2,opt,name=verifyCode,proto3" json:"verifyCode,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterRequest) Reset() {
|
||||
*x = LoginOrRegisterRequest{}
|
||||
mi := &file_contract_protobuf_account_account_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LoginOrRegisterRequest) ProtoMessage() {}
|
||||
|
||||
func (x *LoginOrRegisterRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_contract_protobuf_account_account_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 LoginOrRegisterRequest.ProtoReflect.Descriptor instead.
|
||||
func (*LoginOrRegisterRequest) Descriptor() ([]byte, []int) {
|
||||
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterRequest) GetPhoneNumber() string {
|
||||
if x != nil {
|
||||
return x.PhoneNumber
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterRequest) GetVerifyCode() string {
|
||||
if x != nil {
|
||||
return x.VerifyCode
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type LoginOrRegisterResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
PhoneNumber string `protobuf:"bytes,2,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterResponse) Reset() {
|
||||
*x = LoginOrRegisterResponse{}
|
||||
mi := &file_contract_protobuf_account_account_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LoginOrRegisterResponse) ProtoMessage() {}
|
||||
|
||||
func (x *LoginOrRegisterResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_contract_protobuf_account_account_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 LoginOrRegisterResponse.ProtoReflect.Descriptor instead.
|
||||
func (*LoginOrRegisterResponse) Descriptor() ([]byte, []int) {
|
||||
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterResponse) GetId() uint64 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *LoginOrRegisterResponse) GetPhoneNumber() string {
|
||||
if x != nil {
|
||||
return x.PhoneNumber
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SendOtpRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
PhoneNumber string `protobuf:"bytes,1,opt,name=phoneNumber,proto3" json:"phoneNumber,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SendOtpRequest) Reset() {
|
||||
*x = SendOtpRequest{}
|
||||
mi := &file_contract_protobuf_account_account_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SendOtpRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SendOtpRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SendOtpRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_contract_protobuf_account_account_proto_msgTypes[2]
|
||||
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 SendOtpRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SendOtpRequest) Descriptor() ([]byte, []int) {
|
||||
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SendOtpRequest) GetPhoneNumber() string {
|
||||
if x != nil {
|
||||
return x.PhoneNumber
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SendOtpResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SendOtpResponse) Reset() {
|
||||
*x = SendOtpResponse{}
|
||||
mi := &file_contract_protobuf_account_account_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SendOtpResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SendOtpResponse) ProtoMessage() {}
|
||||
|
||||
func (x *SendOtpResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_contract_protobuf_account_account_proto_msgTypes[3]
|
||||
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 SendOtpResponse.ProtoReflect.Descriptor instead.
|
||||
func (*SendOtpResponse) Descriptor() ([]byte, []int) {
|
||||
return file_contract_protobuf_account_account_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
var File_contract_protobuf_account_account_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_contract_protobuf_account_account_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"'contract/protobuf/account/account.proto\x12\asendOtp\"Z\n" +
|
||||
"\x16LoginOrRegisterRequest\x12 \n" +
|
||||
"\vphoneNumber\x18\x01 \x01(\tR\vphoneNumber\x12\x1e\n" +
|
||||
"\n" +
|
||||
"verifyCode\x18\x02 \x01(\tR\n" +
|
||||
"verifyCode\"K\n" +
|
||||
"\x17LoginOrRegisterResponse\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\x04R\x02id\x12 \n" +
|
||||
"\vphoneNumber\x18\x02 \x01(\tR\vphoneNumber\"2\n" +
|
||||
"\x0eSendOtpRequest\x12 \n" +
|
||||
"\vphoneNumber\x18\x01 \x01(\tR\vphoneNumber\"\x11\n" +
|
||||
"\x0fSendOtpResponse2\xa4\x01\n" +
|
||||
"\x0eAccountService\x12<\n" +
|
||||
"\aSendOtp\x12\x17.sendOtp.SendOtpRequest\x1a\x18.sendOtp.SendOtpResponse\x12T\n" +
|
||||
"\x0fLoginOrRegister\x12\x1f.sendOtp.LoginOrRegisterRequest\x1a .sendOtp.LoginOrRegisterResponseB\x1dZ\x1bcontract/goprotobuf/accountb\x06proto3"
|
||||
|
||||
var (
|
||||
file_contract_protobuf_account_account_proto_rawDescOnce sync.Once
|
||||
file_contract_protobuf_account_account_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_contract_protobuf_account_account_proto_rawDescGZIP() []byte {
|
||||
file_contract_protobuf_account_account_proto_rawDescOnce.Do(func() {
|
||||
file_contract_protobuf_account_account_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_contract_protobuf_account_account_proto_rawDesc), len(file_contract_protobuf_account_account_proto_rawDesc)))
|
||||
})
|
||||
return file_contract_protobuf_account_account_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_contract_protobuf_account_account_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_contract_protobuf_account_account_proto_goTypes = []any{
|
||||
(*LoginOrRegisterRequest)(nil), // 0: sendOtp.LoginOrRegisterRequest
|
||||
(*LoginOrRegisterResponse)(nil), // 1: sendOtp.LoginOrRegisterResponse
|
||||
(*SendOtpRequest)(nil), // 2: sendOtp.SendOtpRequest
|
||||
(*SendOtpResponse)(nil), // 3: sendOtp.SendOtpResponse
|
||||
}
|
||||
var file_contract_protobuf_account_account_proto_depIdxs = []int32{
|
||||
2, // 0: sendOtp.AccountService.SendOtp:input_type -> sendOtp.SendOtpRequest
|
||||
0, // 1: sendOtp.AccountService.LoginOrRegister:input_type -> sendOtp.LoginOrRegisterRequest
|
||||
3, // 2: sendOtp.AccountService.SendOtp:output_type -> sendOtp.SendOtpResponse
|
||||
1, // 3: sendOtp.AccountService.LoginOrRegister:output_type -> sendOtp.LoginOrRegisterResponse
|
||||
2, // [2:4] is the sub-list for method output_type
|
||||
0, // [0:2] 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_contract_protobuf_account_account_proto_init() }
|
||||
func file_contract_protobuf_account_account_proto_init() {
|
||||
if File_contract_protobuf_account_account_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_contract_protobuf_account_account_proto_rawDesc), len(file_contract_protobuf_account_account_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_contract_protobuf_account_account_proto_goTypes,
|
||||
DependencyIndexes: file_contract_protobuf_account_account_proto_depIdxs,
|
||||
MessageInfos: file_contract_protobuf_account_account_proto_msgTypes,
|
||||
}.Build()
|
||||
File_contract_protobuf_account_account_proto = out.File
|
||||
file_contract_protobuf_account_account_proto_goTypes = nil
|
||||
file_contract_protobuf_account_account_proto_depIdxs = nil
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.1
|
||||
// - protoc v3.21.12
|
||||
// source: contract/protobuf/account/account.proto
|
||||
|
||||
package account
|
||||
|
||||
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 (
|
||||
AccountService_SendOtp_FullMethodName = "/sendOtp.AccountService/SendOtp"
|
||||
AccountService_LoginOrRegister_FullMethodName = "/sendOtp.AccountService/LoginOrRegister"
|
||||
)
|
||||
|
||||
// AccountServiceClient is the client API for AccountService 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 AccountServiceClient interface {
|
||||
SendOtp(ctx context.Context, in *SendOtpRequest, opts ...grpc.CallOption) (*SendOtpResponse, error)
|
||||
LoginOrRegister(ctx context.Context, in *LoginOrRegisterRequest, opts ...grpc.CallOption) (*LoginOrRegisterResponse, error)
|
||||
}
|
||||
|
||||
type accountServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAccountServiceClient(cc grpc.ClientConnInterface) AccountServiceClient {
|
||||
return &accountServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *accountServiceClient) SendOtp(ctx context.Context, in *SendOtpRequest, opts ...grpc.CallOption) (*SendOtpResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(SendOtpResponse)
|
||||
err := c.cc.Invoke(ctx, AccountService_SendOtp_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *accountServiceClient) LoginOrRegister(ctx context.Context, in *LoginOrRegisterRequest, opts ...grpc.CallOption) (*LoginOrRegisterResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(LoginOrRegisterResponse)
|
||||
err := c.cc.Invoke(ctx, AccountService_LoginOrRegister_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AccountServiceServer is the server API for AccountService service.
|
||||
// All implementations must embed UnimplementedAccountServiceServer
|
||||
// for forward compatibility.
|
||||
type AccountServiceServer interface {
|
||||
SendOtp(context.Context, *SendOtpRequest) (*SendOtpResponse, error)
|
||||
LoginOrRegister(context.Context, *LoginOrRegisterRequest) (*LoginOrRegisterResponse, error)
|
||||
mustEmbedUnimplementedAccountServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedAccountServiceServer 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 UnimplementedAccountServiceServer struct{}
|
||||
|
||||
func (UnimplementedAccountServiceServer) SendOtp(context.Context, *SendOtpRequest) (*SendOtpResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method SendOtp not implemented")
|
||||
}
|
||||
func (UnimplementedAccountServiceServer) LoginOrRegister(context.Context, *LoginOrRegisterRequest) (*LoginOrRegisterResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method LoginOrRegister not implemented")
|
||||
}
|
||||
func (UnimplementedAccountServiceServer) mustEmbedUnimplementedAccountServiceServer() {}
|
||||
func (UnimplementedAccountServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeAccountServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AccountServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAccountServiceServer interface {
|
||||
mustEmbedUnimplementedAccountServiceServer()
|
||||
}
|
||||
|
||||
func RegisterAccountServiceServer(s grpc.ServiceRegistrar, srv AccountServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedAccountServiceServer 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(&AccountService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _AccountService_SendOtp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SendOtpRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AccountServiceServer).SendOtp(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AccountService_SendOtp_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AccountServiceServer).SendOtp(ctx, req.(*SendOtpRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AccountService_LoginOrRegister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LoginOrRegisterRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AccountServiceServer).LoginOrRegister(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: AccountService_LoginOrRegister_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AccountServiceServer).LoginOrRegister(ctx, req.(*LoginOrRegisterRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AccountService_ServiceDesc is the grpc.ServiceDesc for AccountService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AccountService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "sendOtp.AccountService",
|
||||
HandlerType: (*AccountServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "SendOtp",
|
||||
Handler: _AccountService_SendOtp_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "LoginOrRegister",
|
||||
Handler: _AccountService_LoginOrRegister_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "contract/protobuf/account/account.proto",
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
syntax = "proto3";
|
||||
|
||||
|
||||
package sendOtp;
|
||||
option go_package = "contract/goprotobuf/account";
|
||||
|
||||
message LoginOrRegisterRequest{
|
||||
string phoneNumber = 1;
|
||||
string verifyCode = 2;
|
||||
}
|
||||
|
||||
message LoginOrRegisterResponse {
|
||||
uint64 id = 1;
|
||||
string phoneNumber = 2;
|
||||
}
|
||||
|
||||
|
||||
message SendOtpRequest {
|
||||
string phoneNumber = 1;
|
||||
}
|
||||
|
||||
message SendOtpResponse {}
|
||||
|
||||
service AccountService {
|
||||
rpc SendOtp(SendOtpRequest) returns (SendOtpResponse);
|
||||
rpc LoginOrRegister(LoginOrRegisterRequest) returns(LoginOrRegisterResponse);
|
||||
}
|
||||
|
||||
|
|
@ -18,8 +18,8 @@ type TestContainer struct {
|
|||
dockerPool *dockertest.Pool // the connection pool to Docker.
|
||||
mariaResource *dockertest.Resource // MariaDB Docker container resource.
|
||||
redisResource *dockertest.Resource // Redis Docker container resource.
|
||||
mariaDBConn *mysql.DB // Connection to the MariaDB database.
|
||||
redisDBConn *redisadapter.Adapter // Connection to the Redis database.
|
||||
mariaDBConn *mysql.DB // Connection to the MariaDB mysql.
|
||||
redisDBConn *redisadapter.Adapter // Connection to the Redis mysql.
|
||||
containerExpiryInSeconds uint
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ func (t *TestContainer) Start() {
|
|||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Fatalf("Could not connect to database: %s", err)
|
||||
log.Fatalf("Could not connect to mysql: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
func MigrateMariaDB(cfg mysql.Config) func() {
|
||||
migrations := migrator.New(migrator.Config{
|
||||
MysqlConfig: cfg,
|
||||
MigrationPath: "../../../repository/mysql/migration",
|
||||
MigrationPath: "../../../repository/mysql/migrations",
|
||||
MigrationDBName: "gorp_migrations",
|
||||
})
|
||||
migrations.Up()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
service:
|
||||
length_of_otp_code: 6
|
||||
otp_chars: "0123456789"
|
||||
otp_expire_time: 2
|
||||
redis_db:
|
||||
host:
|
||||
port:
|
||||
password:
|
||||
db:
|
||||
mysql_db:
|
||||
username:
|
||||
password:
|
||||
port:
|
||||
host:
|
||||
db_name:
|
||||
kavenegar:
|
||||
api_key:
|
||||
sender:
|
||||
grpc_server:
|
||||
port:
|
||||
network:
|
||||
grpc_client:
|
||||
host:
|
||||
port:
|
||||
|
||||
|
||||
path_of_migration: ./account/repository/mysql/migration
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
FROM golang:1.25-alpine
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
http_server:
|
||||
host: ""
|
||||
port: 1308
|
||||
shutdown_context_timeout: 10s
|
||||
cors:
|
||||
allow_origins:
|
||||
- "*"
|
||||
|
||||
logger:
|
||||
level: "debug" # Can be `debug`, `info`, `warn`, `error`
|
||||
file_path: "logs/benefactorapp/service.log"
|
||||
use_local_time: true
|
||||
file_max_size_in_mb: 10
|
||||
file_max_age_in_days: 7
|
||||
|
||||
database_retry:
|
||||
max_retries: 3
|
||||
retry_delay: 100ms
|
||||
|
||||
total_shutdown_timeout: 30m
|
||||
|
||||
path_of_migration: "./benefactorapp/repository/migration"
|
||||
|
||||
mariadb:
|
||||
port: 3306
|
||||
host: localhost
|
||||
db_name: niki_db
|
||||
username: niki
|
||||
password: nikiappt0lk2o20
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
service:
|
||||
length_of_otp_code: 6
|
||||
otp_chars: "0123456789"
|
||||
otp_expire_time: 2
|
||||
redis_db:
|
||||
host:
|
||||
port:
|
||||
password:
|
||||
db:
|
||||
mysql_db:
|
||||
username:
|
||||
password:
|
||||
port:
|
||||
host:
|
||||
db_name:
|
||||
kavenegar:
|
||||
api_key:
|
||||
sender:
|
||||
|
||||
path_of_migration: ./driverapp/repository/mysql/migration
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
services:
|
||||
driver_mariadb:
|
||||
image: bitnami/mariadb:11.1
|
||||
container_name: driver_mariadb
|
||||
restart: always
|
||||
ports:
|
||||
- "3305:3306"
|
||||
volumes:
|
||||
- 'driver-mariadb-data:/bitnami/mariadb'
|
||||
environment:
|
||||
MARIADB_USER: driver_admin
|
||||
MARIADB_PASSWORD: password123
|
||||
MARIADB_DATABASE: driver_db
|
||||
MARIADB_ROOT_PASSWORD: password123
|
||||
driver_redis:
|
||||
image: bitnami/redis:6.2
|
||||
container_name: driver-redis
|
||||
restart: always
|
||||
ports:
|
||||
- '6380:6379'
|
||||
command: redis-server --loglevel warning --protected-mode no --save "" --appendonly no
|
||||
environment:
|
||||
- ALLOW_EMPTY_PASSWORD=yes
|
||||
volumes:
|
||||
- driver-redis-data:/data
|
||||
|
||||
volumes:
|
||||
driver-mariadb-data:
|
||||
driver-redis-data:
|
||||
|
|
@ -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:
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
redis:
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
password: ""
|
||||
db: 0
|
||||
|
||||
repo:
|
||||
kart_key_prefix: "shopping-basket-cart:"
|
||||
ttl: 3600s
|
||||
|
||||
http_server:
|
||||
host: "localhost"
|
||||
port: 8080
|
||||
shutdown_context_timeout: 10s
|
||||
cors:
|
||||
allow_origins:
|
||||
- "*"
|
||||
|
||||
|
||||
logger:
|
||||
level: "debug" # Can be `debug`, `info`, `warn`, `error`
|
||||
file_path: "logs/shoppingbasketapp/service.log"
|
||||
use_local_time: true
|
||||
file_max_size_in_mb: 10
|
||||
file_max_age_in_days: 7
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package entity
|
||||
|
||||
import "git.gocasts.ir/ebhomengo/niki/pkg/types"
|
||||
|
||||
type Driver struct {
|
||||
ID types.ID
|
||||
PhoneNumber string
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/account/entity"
|
||||
"git.gocasts.ir/ebhomengo/niki/pkg/database/mysql"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
types "git.gocasts.ir/ebhomengo/niki/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
StatementKeyIsExistDriverByPhoneNumber = iota + 1
|
||||
StatementKeyCreateDriver = iota + 1
|
||||
)
|
||||
|
||||
type AccountRepo struct {
|
||||
db *mysql.DB
|
||||
}
|
||||
|
||||
func New(db *mysql.DB) AccountRepo {
|
||||
return AccountRepo{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r AccountRepo) IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error) {
|
||||
const op = "Repository.IsExistDriverByPhoneNumber"
|
||||
query := `select * from drivers where phone_number = ?`
|
||||
stmt, err := r.db.PrepareStatement(ctx, StatementKeyIsExistDriverByPhoneNumber, query)
|
||||
if err != nil {
|
||||
return false, entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
||||
WithMessage(errmsg.ErrorMsgCantPrepareStatement)
|
||||
}
|
||||
|
||||
defer stmt.Close()
|
||||
|
||||
row := stmt.QueryRowContext(ctx, phoneNumber)
|
||||
d, sErr := DriverScan(row)
|
||||
if sErr != nil {
|
||||
if errors.Is(sErr, sql.ErrNoRows) {
|
||||
return false, entity.Driver{}, richerror.New(op).WithKind(richerror.KindNotFound).
|
||||
WithMessage(errmsg.ErrorMsgNotFound)
|
||||
}
|
||||
return false, entity.Driver{}, richerror.New(op).WithErr(err).
|
||||
WithMessage(errmsg.ErrorMsgCantScanQueryResult).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
return true, d, nil
|
||||
|
||||
}
|
||||
|
||||
func (r AccountRepo) CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error) {
|
||||
const op = "Repository.CreateDriver"
|
||||
query := `insert into drivers(phone_number) values(?)`
|
||||
|
||||
stmt, err := r.db.PrepareStatement(ctx, StatementKeyCreateDriver, query)
|
||||
if err != nil {
|
||||
return entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
||||
WithMessage(errmsg.ErrorMsgCantPrepareStatement)
|
||||
}
|
||||
|
||||
res, err := stmt.ExecContext(ctx, driver.PhoneNumber)
|
||||
|
||||
if err != nil {
|
||||
return entity.Driver{}, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected).
|
||||
WithMessage(errmsg.ErrorMsgNotFound)
|
||||
}
|
||||
|
||||
id, _ := res.LastInsertId()
|
||||
driver.ID = types.ID(id)
|
||||
|
||||
return driver, nil
|
||||
}
|
||||
|
||||
func DriverScan(scanner mysql.Scanner) (entity.Driver, error) {
|
||||
var createdAt, updatedAt time.Time
|
||||
var driver entity.Driver
|
||||
|
||||
err := scanner.Scan(&driver.ID, &driver.PhoneNumber, &createdAt, &updatedAt)
|
||||
|
||||
return driver, err
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
-- +migrate Up
|
||||
CREATE TABLE `drivers`(
|
||||
`iD` INT PRIMARY KEY AUTO_INCREMENT,
|
||||
`phone_number` VARCHAR(191) NOT NULL UNIQUE ,
|
||||
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
|
||||
);
|
||||
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `drivers`;
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/adapter/redis"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
)
|
||||
|
||||
type RepositoryOtp struct {
|
||||
conn *redis.Adapter
|
||||
}
|
||||
|
||||
func NewRepositoryOtp(conn *redis.Adapter) RepositoryOtp {
|
||||
return RepositoryOtp{conn: conn}
|
||||
}
|
||||
|
||||
func (r RepositoryOtp) IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error) {
|
||||
const op = "RepositoryOtp.IsExistPhoneNumber"
|
||||
|
||||
result, err := r.conn.Client().Exists(ctx, phoneNumber).Result()
|
||||
if err != nil {
|
||||
return false, richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
if result == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r RepositoryOtp) SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error {
|
||||
const op = "RepositoryOtp.SaveCodeWithPhoneNumber"
|
||||
|
||||
_, err := r.conn.Client().Set(ctx, phoneNumber, code, expireTime).Result()
|
||||
if err != nil {
|
||||
return richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r RepositoryOtp) GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error) {
|
||||
const op = "RepositoryOtp.GetCodeByPhoneNumber"
|
||||
|
||||
result, err := r.conn.Client().Get(ctx, phoneNumber).Result()
|
||||
if err != nil {
|
||||
return "", richerror.New(op).WithKind(richerror.KindUnexpected).WithErr(err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r RepositoryOtp) DeleteCodeByPhoneNumber(ctx context.Context, PhoneNumber string) (bool, error) {
|
||||
const op = "RepositoryOtp.DeleteCodeByPhoneNumber"
|
||||
success, err := r.conn.Client().Del(ctx, PhoneNumber).Result()
|
||||
if err != nil {
|
||||
return false, richerror.New(op).WithErr(err).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
if success != 1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
smscontract "git.gocasts.ir/ebhomengo/niki/contract/sms"
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/account/entity"
|
||||
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
|
||||
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
LengthOfOtpCode int `koanf:"length_of_otp_code"`
|
||||
OtpChars string `koanf:"otp_chars"`
|
||||
OtpExpireTime time.Duration `koanf:"otp_expire_time"`
|
||||
}
|
||||
|
||||
type RepositoryOtp interface {
|
||||
IsExistPhoneNumber(ctx context.Context, phoneNumber string) (bool, error)
|
||||
SaveCodeWithPhoneNumber(ctx context.Context, phoneNumber string, code string, expireTime time.Duration) error
|
||||
GetCodeByPhoneNumber(ctx context.Context, phoneNumber string) (string, error)
|
||||
DeleteCodeByPhoneNumber(ctx context.Context, PhoneNumber string) (bool, error)
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
IsExistDriverByPhoneNumber(ctx context.Context, phoneNumber string) (bool, entity.Driver, error)
|
||||
CreateDriver(ctx context.Context, driver entity.Driver) (entity.Driver, error)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
config Config
|
||||
repositoryOtp RepositoryOtp
|
||||
repository Repository
|
||||
smsContract smscontract.SmsAdapter
|
||||
}
|
||||
|
||||
func NewService(cfg Config, repositoryOtp RepositoryOtp, repository Repository, smsContract smscontract.SmsAdapter) Service {
|
||||
return Service{
|
||||
config: cfg,
|
||||
repositoryOtp: repositoryOtp,
|
||||
repository: repository,
|
||||
smsContract: smsContract,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) SendOTP(ctx context.Context, phoneNumber string) error {
|
||||
const op = "accountService.SendOTP"
|
||||
|
||||
isExist, iErr := s.repositoryOtp.IsExistPhoneNumber(ctx, phoneNumber)
|
||||
if iErr != nil {
|
||||
return richerror.New(op).WithErr(iErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
if isExist {
|
||||
return richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeExist).WithKind(richerror.KindForbidden)
|
||||
}
|
||||
|
||||
newCode := s.generateVerificationCode()
|
||||
sErr := s.repositoryOtp.SaveCodeWithPhoneNumber(ctx, phoneNumber, newCode, s.config.OtpExpireTime)
|
||||
if sErr != nil {
|
||||
return richerror.New(op).WithErr(sErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
go s.smsContract.Send(phoneNumber, newCode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Service) LoginOrRegisterDriver(ctx context.Context, phoneNumber string, verifyCode string) (entity.Driver, error) {
|
||||
const op = "accountService.LoginOrRegisterDriver"
|
||||
|
||||
code, gErr := s.repositoryOtp.GetCodeByPhoneNumber(ctx, phoneNumber)
|
||||
if gErr != nil {
|
||||
return entity.Driver{}, richerror.New(op).WithErr(gErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
if code == "" || code != verifyCode {
|
||||
return entity.Driver{}, richerror.New(op).WithMessage(errmsg.ErrorMsgOtpCodeIsNotValid).WithKind(richerror.KindForbidden)
|
||||
}
|
||||
|
||||
_, dErr := s.repositoryOtp.DeleteCodeByPhoneNumber(ctx, phoneNumber)
|
||||
if dErr != nil {
|
||||
return entity.Driver{}, richerror.New(op).WithErr(dErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
isExist, driver, eErr := s.repository.IsExistDriverByPhoneNumber(ctx, phoneNumber)
|
||||
if eErr != nil {
|
||||
return entity.Driver{}, richerror.New(op).WithErr(eErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
if !isExist {
|
||||
newDriver, cErr := s.repository.CreateDriver(ctx, entity.Driver{
|
||||
PhoneNumber: phoneNumber,
|
||||
})
|
||||
if cErr != nil {
|
||||
return entity.Driver{}, richerror.New(op).WithErr(cErr).WithKind(richerror.KindUnexpected)
|
||||
}
|
||||
|
||||
driver = newDriver
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
|
||||
}
|
||||
|
||||
func (s Service) generateVerificationCode() string {
|
||||
result := make([]byte, s.config.LengthOfOtpCode)
|
||||
for i := 0; i < s.config.LengthOfOtpCode; i++ {
|
||||
result[i] = s.config.OtpChars[rand.Intn(len(s.config.OtpChars))]
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"git.gocasts.ir/ebhomengo/niki/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CampaignStatus string
|
||||
|
||||
const (
|
||||
CampaignDraft CampaignStatus = "draft"
|
||||
CampaignActive CampaignStatus = "active"
|
||||
CampaignFinished CampaignStatus = "completed"
|
||||
CampaignPaused CampaignStatus = "paused"
|
||||
CampaignCanceled CampaignStatus = "cancelled"
|
||||
)
|
||||
|
||||
type Campaign struct {
|
||||
ID types.ID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
GoalAmount float64 `json:"goal_amount"`
|
||||
RaisedAmount float64 `json:"raised_amount"`
|
||||
Status CampaignStatus `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DeadlineAt *time.Time `json:"deadline_at,omitempty"`
|
||||
AdminID types.ID `json:"creator_id"`
|
||||
}
|
||||
|
||||
// Behavior
|
||||
func (c *Campaign) Activate() {
|
||||
if c.Status == CampaignDraft {
|
||||
c.Status = CampaignActive
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Campaign) AddFunds(amount float64) {
|
||||
c.RaisedAmount += amount
|
||||
if c.RaisedAmount >= c.GoalAmount {
|
||||
c.Status = CampaignFinished
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Campaign) IsExpired(now time.Time) bool {
|
||||
if c.DeadlineAt == nil {
|
||||
return false
|
||||
}
|
||||
return now.After(*c.DeadlineAt)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
-- +migrate Up
|
||||
|
||||
CREATE TABLE `campaigns` (
|
||||
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
`title` VARCHAR(255) NOT NULL,
|
||||
`description` TEXT,
|
||||
`goal_amount` DECIMAL(15,2) NOT NULL,
|
||||
`raised_amount` DECIMAL(15,2) DEFAULT 0,
|
||||
`status` VARCHAR(50) NOT NULL,
|
||||
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`deadline_at` TIMESTAMP,
|
||||
`admin_id` BIGINT NOT NULL,
|
||||
FOREIGN KEY (`admin_id`) REFERENCES `users`(`id`) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE `campaigns`;
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
)
|
||||
|
||||
// CreateCampaign handles creation of a new 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 {
|
||||
return 0, richerror.New(op).WithErr(err)
|
||||
}
|
||||
|
||||
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 {
|
||||
if req.Title == "" {
|
||||
return errRequired("title")
|
||||
}
|
||||
if req.GoalAmount <= 0 {
|
||||
return errInvalid("goal_amount must be greater than 0")
|
||||
}
|
||||
if req.AdminID == 0 {
|
||||
return errRequired("admin_id")
|
||||
}
|
||||
|
||||
validStatuses := map[string]bool{
|
||||
"draft": true,
|
||||
"active": true,
|
||||
"completed": true,
|
||||
"cancelled": true,
|
||||
"paused": true,
|
||||
}
|
||||
if !validStatuses[string(req.Status)] {
|
||||
return errInvalid("invalid status provided")
|
||||
}
|
||||
|
||||
return 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,8 @@
|
|||
package entity
|
||||
|
||||
type Channel struct {
|
||||
ID int8
|
||||
Type NotificationType
|
||||
Provider string
|
||||
Config string
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package entity
|
||||
|
||||
type Notification struct {
|
||||
ID int8
|
||||
Type NotificationType
|
||||
Recipinet string
|
||||
Body string
|
||||
Status NotificationStatus
|
||||
}
|
||||
|
||||
type NotificationType uint8
|
||||
|
||||
const (
|
||||
Email NotificationType = iota + 1
|
||||
SMS
|
||||
Push
|
||||
)
|
||||
|
||||
func (t NotificationType) String() string {
|
||||
switch t {
|
||||
case Email:
|
||||
return "Email"
|
||||
case SMS:
|
||||
return "SMS"
|
||||
case Push:
|
||||
return "Push"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type NotificationStatus uint8
|
||||
|
||||
const (
|
||||
Pending NotificationStatus = iota + 1
|
||||
Success
|
||||
Failed
|
||||
)
|
||||
|
||||
func (t NotificationStatus) String() string {
|
||||
switch t {
|
||||
case Pending:
|
||||
return "Pending"
|
||||
case Success:
|
||||
return "Success"
|
||||
case Failed:
|
||||
return "Failed"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package messagebroker
|
||||
|
||||
import "git.gocasts.ir/ebhomengo/niki/domain/notification/entity"
|
||||
|
||||
type redis struct {
|
||||
}
|
||||
|
||||
func (r *redis) AddItem(notification entity.Notification) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *redis) RemoveItem(notification entity.Notification) error {
|
||||
return nil
|
||||
}
|
||||
func (r *redis) HealthCheck() error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
_ "time"
|
||||
|
||||
"git.gocasts.ir/ebhomengo/niki/domain/notification/entity"
|
||||
)
|
||||
|
||||
type Notifservice struct {
|
||||
MessageBroker MessageBroker
|
||||
Repository Repository
|
||||
Channel Channel
|
||||
}
|
||||
|
||||
type MessageBroker interface {
|
||||
AddItem(notification entity.Notification) error
|
||||
RemoveItem(notification entity.Notification) error
|
||||
HealthCheck() error
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
AddItem(notification entity.Notification) error
|
||||
}
|
||||
|
||||
type Channel interface {
|
||||
SendMessage(notification entity.Notification) error
|
||||
}
|
||||
type NotificationServiceRequest struct {
|
||||
Body string
|
||||
Type entity.NotificationType
|
||||
Recipinet string
|
||||
}
|
||||
|
||||
func (n *Notifservice) NewNotification(r NotificationServiceRequest) *entity.Notification {
|
||||
// TODO add validation of notification properties
|
||||
return &entity.Notification{
|
||||
Type: r.Type,
|
||||
Recipinet: r.Recipinet,
|
||||
Body: r.Body,
|
||||
Status: entity.Pending,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notifservice) Send(notification entity.Notification) error {
|
||||
if err := n.Channel.SendMessage(notification); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Notifservice) Add(notification *entity.Notification) error {
|
||||
err := n.MessageBroker.AddItem(*notification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Notifservice) Archive(notification *entity.Notification) error {
|
||||
if err := n.MessageBroker.RemoveItem(*notification); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := n.Repository.AddItem(*notification); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue