forked from ebhomengo/niki
Merge branch 'develop' into feat-benefactorcmd-pkg
This commit is contained in:
commit
0cc151ba0c
|
|
@ -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,54 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"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...")
|
||||||
|
|
||||||
|
// TODO: Initialize database connection
|
||||||
|
// TODO: Initialize service dependencies
|
||||||
|
// TODO: Setup HTTP server with routes
|
||||||
|
|
||||||
|
// Setup graceful shutdown
|
||||||
|
go func() {
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigCh
|
||||||
|
log.Println("Shutting down Product Service gracefully...")
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintf(w, "Product Service OK!")
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Product Service listening on port %s", port)
|
||||||
|
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
||||||
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,63 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http"
|
||||||
|
purchaseMysql "git.gocasts.ir/ebhomengo/niki/purchaseapp/repository/mysql"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/migrator"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MariaDB() *mysql.DB {
|
||||||
|
cfg := mysql.Config{
|
||||||
|
Username: "niki",
|
||||||
|
Password: "nikiappt0lk2o20",
|
||||||
|
Port: 3306,
|
||||||
|
Host: "localhost",
|
||||||
|
DBName: "niki_db",
|
||||||
|
}
|
||||||
|
migrate := flag.Bool("migrate", false, "perform database migration")
|
||||||
|
flag.Parse()
|
||||||
|
if *migrate {
|
||||||
|
migrator.New(migrator.Config{
|
||||||
|
MysqlConfig: cfg,
|
||||||
|
MigrationPath: "./purchaseapp/repository/mysql/migration",
|
||||||
|
MigrationDBName: "gorp_migrations",
|
||||||
|
}).Up()
|
||||||
|
}
|
||||||
|
|
||||||
|
return mysql.New(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := mysql.Config{
|
||||||
|
Username: "niki",
|
||||||
|
Password: "nikiappt0lk2o20",
|
||||||
|
Port: 3306,
|
||||||
|
Host: "localhost",
|
||||||
|
DBName: "niki_db",
|
||||||
|
}
|
||||||
|
db := mysql.New(cfg)
|
||||||
|
defer func() {
|
||||||
|
if err := db.CloseStatements(); err != nil {
|
||||||
|
fmt.Printf("Error closing statements: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
orderRepo := purchaseMysql.New(db)
|
||||||
|
|
||||||
|
orderSvc := Service(orderRepo)
|
||||||
|
server := HTTPServer(orderSvc)
|
||||||
|
server.Serve()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPServer(orderSvc order.Service) *http.Server {
|
||||||
|
return http.New(orderSvc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Service(orderRepo *purchaseMysql.DB) order.Service {
|
||||||
|
return order.New(orderRepo)
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,143 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/staffapp/repository/database"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/staffapp/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(" Staffapp Server Starting...")
|
|
||||||
|
staffDb := database.New()
|
||||||
|
staffService := service.NewStaffService(staffDb)
|
||||||
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "Staffapp OK!")
|
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))
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultPrefix = "EB_"
|
defaultPrefix = "EB_"
|
||||||
defaultDelimiter = "."
|
defaultDelimiter = "."
|
||||||
defaultSeparator = "__"
|
defaultSeparator = "__"
|
||||||
defaultYamlFilePath = "config.yml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultYamlFilePath = "config.yml"
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ type TestContainer struct {
|
||||||
dockerPool *dockertest.Pool // the connection pool to Docker.
|
dockerPool *dockertest.Pool // the connection pool to Docker.
|
||||||
mariaResource *dockertest.Resource // MariaDB Docker container resource.
|
mariaResource *dockertest.Resource // MariaDB Docker container resource.
|
||||||
redisResource *dockertest.Resource // Redis Docker container resource.
|
redisResource *dockertest.Resource // Redis Docker container resource.
|
||||||
mariaDBConn *mysql.DB // Connection to the MariaDB database.
|
mariaDBConn *mysql.DB // Connection to the MariaDB mysql.
|
||||||
redisDBConn *redisadapter.Adapter // Connection to the Redis database.
|
redisDBConn *redisadapter.Adapter // Connection to the Redis mysql.
|
||||||
containerExpiryInSeconds uint
|
containerExpiryInSeconds uint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +158,7 @@ func (t *TestContainer) Start() {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Fatalf("Could not connect to database: %s", err)
|
log.Fatalf("Could not connect to mysql: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import "git.gocasts.ir/ebhomengo/niki/domain/notification/entity"
|
||||||
|
|
||||||
|
type sender interface {
|
||||||
|
Send(notification entity.Notification) error
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package donate
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package donateapp
|
|
||||||
|
|
||||||
type Config struct{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package donate_server
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHandler() Handler {
|
|
||||||
return Handler{}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
-- +migrate Up
|
|
||||||
CREATE TABLE `donates` (
|
|
||||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT,
|
|
||||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`) USING BTREE,
|
|
||||||
UNIQUE INDEX `id`(`id` ASC) USING BTREE
|
|
||||||
) ENGINE = InnoDB AUTO_INCREMENT = 84 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_persian_ci ROW_FORMAT = Dynamic;
|
|
||||||
|
|
||||||
-- +migrate Down
|
|
||||||
DROP TABLE IF EXISTS `donates`;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package mysql
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package doanteApp
|
package donate_app
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
|
|
@ -6,3 +6,5 @@ type Application struct {
|
||||||
Config Config
|
Config Config
|
||||||
HTTPServer *http.Server
|
HTTPServer *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package donate_app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Mysql mysql.Config `koanf:"mariadb"`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Config struct{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package donate_server
|
||||||
|
|
||||||
|
type Handler struct{}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
// --- Type Aliases ---
|
||||||
|
package pkg
|
||||||
|
|
||||||
|
type ID uint64
|
||||||
|
|
@ -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,19 @@
|
||||||
|
-- +migrate Up
|
||||||
|
|
||||||
|
CREATE TABLE `campaign_participants` (
|
||||||
|
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`campaign_id` BIGINT NOT NULL,
|
||||||
|
`user_id` BIGINT NOT NULL,
|
||||||
|
`amount` DECIMAL(15,2) NOT NULL,
|
||||||
|
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (`campaign_id`)
|
||||||
|
REFERENCES `campaigns`(`id`)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (`user_id`)
|
||||||
|
REFERENCES `users`(`id`)
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
UNIQUE KEY `unique_campaign_user` (`campaign_id`, `user_id`)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE `campaign_participants`;
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/campaign/entity"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
conn *mysql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db *mysql.DB) *DB {
|
||||||
|
return &DB{conn: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCampaign creates a new campaign
|
||||||
|
func (d *DB) CreateCampaign(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create adds a new participant to a campaign
|
||||||
|
func (d *DB) CreateCampaignParticipants(ctx context.Context, participant entity.CampaignParticipant) (types.ID, error) {
|
||||||
|
const Op = "repository.mysql.campaign_participant.create"
|
||||||
|
|
||||||
|
query := `INSERT INTO campaign_participants (id,campaign_id, user_id, amount , created_at)
|
||||||
|
VALUES (?, ?, ? , ? , NOW())`
|
||||||
|
|
||||||
|
result, err := d.conn.ExecContext(ctx, query,
|
||||||
|
participant.ID,
|
||||||
|
participant.CampaignID,
|
||||||
|
participant.UserID,
|
||||||
|
participant.Amount,
|
||||||
|
participant.CreatedAt
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := result.LastInsertId()
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ID(id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
|
||||||
|
type ID uint64
|
||||||
|
|
||||||
|
|
||||||
|
type CampaignParticipant struct {
|
||||||
|
ID ID `json:"id"`
|
||||||
|
CampaignID ID `json:"campaign_id"`
|
||||||
|
UserID ID `json:"user_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CampaignStatus represents the possible states of a campaign
|
||||||
|
type CampaignStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusActive CampaignStatus = "active"
|
||||||
|
StatusCompleted CampaignStatus = "completed"
|
||||||
|
StatusCancelled CampaignStatus = "cancelled"
|
||||||
|
StatusExpired CampaignStatus = "expired" // New status for deadlines
|
||||||
|
)
|
||||||
|
|
||||||
|
type ID uint64
|
||||||
|
|
||||||
|
type Campaign struct {
|
||||||
|
ID 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 ID `json:"creator_id"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/campaign/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type CampaignService struct {
|
||||||
|
repo repository.CampaignRepository
|
||||||
|
participantRepo repository.CampaignParticipantRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type CampaignServiceInterface interface {
|
||||||
|
CreateCampaign(ctx context.Context, req CampaignRepository) (types.ID, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCampaignService creates a new campaign service
|
||||||
|
func NewCampaignService(
|
||||||
|
repo repository.CampaignRepository,
|
||||||
|
participantRepo repository.CampaignParticipantRepository,
|
||||||
|
) *CampaignService {
|
||||||
|
return &CampaignService{
|
||||||
|
repo: repo,
|
||||||
|
participantRepo: participantRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// CreateCampaign creates a new campaign
|
||||||
|
func (s *CampaignService) CreateCampaign(ctx context.Context, req CreateCampaignRequest) (types.ID, error) {
|
||||||
|
const Op = "service.campaign.create_campaign"
|
||||||
|
|
||||||
|
// Business Logic: Validation
|
||||||
|
if req.Title == "" {
|
||||||
|
return 0, richerror.New(Op).WithErr(errors.New("title is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.GoalAmount <= 0 {
|
||||||
|
return 0, richerror.New(Op).WithErr(errors.New("goal_amount must be greater than 0"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.AdminID == 0 {
|
||||||
|
return 0, richerror.New(Op).WithErr(errors.New("admin_id is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Business Logic: Validate status
|
||||||
|
validStatuses := map[string]bool{
|
||||||
|
"draft": true,
|
||||||
|
"active": true,
|
||||||
|
"completed": true,
|
||||||
|
"cancelled": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validStatuses[req.Status] {
|
||||||
|
return 0, richerror.New(Op).WithErr(errors.New("invalid status"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create campaign entity
|
||||||
|
campaign := entity.Campaign{
|
||||||
|
Title: req.Title,
|
||||||
|
Description: req.Description,
|
||||||
|
GoalAmount: req.GoalAmount,
|
||||||
|
RaisedAmount: 0, // Initially 0
|
||||||
|
Status: entity.CampaignStatus(req.Status),
|
||||||
|
DeadlineAt: req.DeadlineAt,
|
||||||
|
AdminID: req.AdminID,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call repository
|
||||||
|
campaignID, err := s.repo.CreateCampaign(ctx, campaign)
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return campaignID, nil
|
||||||
|
}
|
||||||
14
go.mod
14
go.mod
|
|
@ -21,7 +21,7 @@ require (
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/swaggo/echo-swagger v1.5.2
|
github.com/swaggo/echo-swagger v1.5.2
|
||||||
github.com/swaggo/swag v1.16.6
|
github.com/swaggo/swag v1.16.6
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.48.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -82,13 +82,13 @@ require (
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
golang.org/x/mod v0.30.0 // indirect
|
golang.org/x/mod v0.33.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.35.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.39.0 // indirect
|
golang.org/x/tools v0.42.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
|
|
||||||
28
go.sum
28
go.sum
|
|
@ -418,8 +418,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
|
@ -429,8 +429,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
|
@ -448,8 +448,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
|
@ -462,8 +462,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -497,8 +497,8 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
|
@ -506,8 +506,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
|
|
@ -522,8 +522,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -43,7 +43,7 @@ func Config() config.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
func MariaDB(cfg config.Config) *mysql.DB {
|
func MariaDB(cfg config.Config) *mysql.DB {
|
||||||
migrate := flag.Bool("migrate", false, "perform database migration")
|
migrate := flag.Bool("migrate", false, "perform mysql migration")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *migrate {
|
if *migrate {
|
||||||
migrator.New(migrator.Config{
|
migrator.New(migrator.Config{
|
||||||
|
|
|
||||||
|
|
@ -1 +1,37 @@
|
||||||
package patientapp
|
package patientapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/config"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/delivery/http/analytic"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/repository/mysql"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
//Config Config
|
||||||
|
HTTPServer *config.EchoServer
|
||||||
|
DB *mysql.DataBase
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(cfg config.Config, conn *mysql.DataBase) Application {
|
||||||
|
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
server := config.EchoServer{
|
||||||
|
Router: e,
|
||||||
|
Config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Application{
|
||||||
|
//Config: config,
|
||||||
|
HTTPServer: &server,
|
||||||
|
DB: conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Application) Start() {
|
||||||
|
|
||||||
|
server := analytic.NewServer(a.HTTPServer)
|
||||||
|
|
||||||
|
_ = server.Serve()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/config"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/repository/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db := mysql.DataBase{}
|
||||||
|
|
||||||
|
cfg := config.Config{
|
||||||
|
Port: 8080,
|
||||||
|
Cors: config.Cors{
|
||||||
|
AllowOrigins: []string{"*"},
|
||||||
|
},
|
||||||
|
ShutDownCtxTimeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
app := patientapp.Setup(cfg, &db)
|
||||||
|
|
||||||
|
app.Start()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Port int `koanf:"port"`
|
||||||
|
Cors Cors `koanf:"cors"`
|
||||||
|
ShutDownCtxTimeout time.Duration `koanf:"shutdown_context_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cors struct {
|
||||||
|
AllowOrigins []string `koanf:"allow_origins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EchoServer struct {
|
||||||
|
Router *echo.Echo
|
||||||
|
Config Config
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
svc "git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
service svc.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(service svc.Service) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Health(e echo.Context) error {
|
||||||
|
return e.JSON(http.StatusOK, map[string]interface{}{"status": "ok"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PatientsAnalytic(e echo.Context) error {
|
||||||
|
var req svc.ListPatientAnalyticRequest
|
||||||
|
|
||||||
|
richErr := richerror.New(richerror.Op("fetchingPatientList.PatientsAnalytic"))
|
||||||
|
|
||||||
|
if err := e.Bind(&req); err != nil {
|
||||||
|
richErr = richErr.WithErr(err)
|
||||||
|
richErr = richErr.WithKind(1)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, richErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.List(e.Request().Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
richErr = richErr.WithErr(err)
|
||||||
|
richErr = richErr.WithKind(4)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, richErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) PatientsMapSummary(e echo.Context) error {
|
||||||
|
richErr := richerror.New(richerror.Op("fetchingPatientMapSummary.PatientsMapSummary"))
|
||||||
|
|
||||||
|
var req svc.GetPatientMapSummaryRequest
|
||||||
|
|
||||||
|
if err := e.Bind(&req); err != nil {
|
||||||
|
richErr = richErr.WithErr(err)
|
||||||
|
richErr = richErr.WithKind(1)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, richErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, svcErr := h.service.GetMapSummary(e.Request().Context(), req)
|
||||||
|
if svcErr != nil {
|
||||||
|
richErr = richErr.WithErr(svcErr)
|
||||||
|
richErr = richErr.WithKind(4)
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, richErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/repository/mysql"
|
||||||
|
analytic2 "git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPatientAnalyticRouter(s *echo.Group) {
|
||||||
|
|
||||||
|
mysqlRepo := mysql.NewPatientRepo()
|
||||||
|
//rpcRepo := grpc.NewPatientRepo()
|
||||||
|
|
||||||
|
analyticService := analytic2.NewPatientAnalyticService(mysqlRepo)
|
||||||
|
|
||||||
|
h := NewHandler(analyticService)
|
||||||
|
|
||||||
|
s.GET("/patients", h.PatientsAnalytic)
|
||||||
|
s.GET("/patients-summary", h.PatientsMapSummary)
|
||||||
|
s.GET("/health", h.Health)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
HTTPServer *config.EchoServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(server *config.EchoServer) *Server {
|
||||||
|
|
||||||
|
return &Server{
|
||||||
|
HTTPServer: server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) Serve() error {
|
||||||
|
s.RegisterRoutes()
|
||||||
|
// Start server
|
||||||
|
return s.HTTPServer.Router.Start(fmt.Sprintf(":%d", s.HTTPServer.Config.Port))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) Stop(ctx context.Context) error {
|
||||||
|
return s.HTTPServer.Router.Shutdown(ctx)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Server) RegisterRoutes() {
|
||||||
|
|
||||||
|
v1 := s.HTTPServer.Router.Group("/v1")
|
||||||
|
{
|
||||||
|
// Analytic Group
|
||||||
|
analyticGroup := v1.Group("/analytic")
|
||||||
|
NewPatientAnalyticRouter(analyticGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnalyticRepository struct{}
|
||||||
|
|
||||||
|
func NewPatientRepo() *AnalyticRepository {
|
||||||
|
|
||||||
|
return &AnalyticRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *AnalyticRepository) GetPatients(ctx context.Context, f analytic.PatientFilter) ([]entity.Patient, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *AnalyticRepository) CountPatients(ctx context.Context, f analytic.PatientFilter) (int, error) {
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *AnalyticRepository) SummaryByCity(ctx context.Context, provinceID uint, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *AnalyticRepository) SummaryByProvince(ctx context.Context, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/analytic"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataBase struct{}
|
||||||
|
|
||||||
|
func NewPatientRepo() *DataBase {
|
||||||
|
|
||||||
|
return &DataBase{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DataBase) GetPatients(ctx context.Context, f analytic.PatientFilter) ([]entity.Patient, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DataBase) CountPatients(ctx context.Context, f analytic.PatientFilter) (int, error) {
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DataBase) SummaryByCity(ctx context.Context, provinceID uint, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *DataBase) SummaryByProvince(ctx context.Context, f analytic.PatientMapFilter) (map[uint][]entity.MapSummaryItem, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jalaali/go-jalaali"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func normalizeLimitOffset(limit, offset int) (int, int) {
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 50
|
||||||
|
}
|
||||||
|
if limit > 100 {
|
||||||
|
limit = 100
|
||||||
|
}
|
||||||
|
if offset < 0 {
|
||||||
|
offset = 0
|
||||||
|
}
|
||||||
|
return limit, offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert age range -> DOB range
|
||||||
|
func ageRangeToDOB(minAge, maxAge *int, now time.Time) (dobFrom, dobTo *string) {
|
||||||
|
if maxAge != nil {
|
||||||
|
t := now.AddDate(-(*maxAge + 1), 0, 1)
|
||||||
|
jy, jm, jd, err := jalaali.ToJalaali(t.Year(), t.Month(), t.Day())
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("%04d/%02d/%02d", jy, jm, jd)
|
||||||
|
dobFrom = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
if minAge != nil {
|
||||||
|
t := now.AddDate(-*minAge, 0, 0)
|
||||||
|
jy, jm, jd, err := jalaali.ToJalaali(t.Year(), t.Month(), t.Day())
|
||||||
|
if err != nil {
|
||||||
|
}
|
||||||
|
s := fmt.Sprintf("%04d/%02d/%02d", jy, jm, jd)
|
||||||
|
dobTo = &s
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListPatientAnalyticRequest struct {
|
||||||
|
// All fields are optional
|
||||||
|
MinAge *int `query:"minAge,omitempty"`
|
||||||
|
MaxAge *int `query:"maxAge,omitempty"`
|
||||||
|
Sex *entity.Sex `query:"sex,omitempty"`
|
||||||
|
|
||||||
|
City *int64 `query:"city,omitempty"`
|
||||||
|
Province *int64 `query:"province,omitempty"`
|
||||||
|
|
||||||
|
Search *string `query:"search,omitempty"`
|
||||||
|
|
||||||
|
Pagination *Pagination `query:"pagination,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatientAnalyticItem struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"Last_name"`
|
||||||
|
DateOfBirth string `json:"dob,omitempty"`
|
||||||
|
Sex entity.Sex `json:"sex"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Address entity.Address `json:"address"`
|
||||||
|
}
|
||||||
|
type PatientAnalyticResponse struct {
|
||||||
|
Items []PatientAnalyticItem `json:"items"`
|
||||||
|
Pagination *Pagination `json:"pagination"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPatientResponse(patient entity.Patient) PatientAnalyticItem {
|
||||||
|
return PatientAnalyticItem{
|
||||||
|
ID: patient.ID,
|
||||||
|
FirstName: patient.FirstName,
|
||||||
|
LastName: patient.LastName,
|
||||||
|
DateOfBirth: patient.DateOfBirth,
|
||||||
|
Sex: patient.Sex,
|
||||||
|
Phone: patient.Phone,
|
||||||
|
Address: entity.Address{
|
||||||
|
ProvinceID: patient.Address.ProvinceID,
|
||||||
|
CityID: patient.Address.CityID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPatientMapSummaryRequest =========================== Map ==================================
|
||||||
|
type GetPatientMapSummaryRequest struct {
|
||||||
|
Level entity.MapLevel `query:"level"`
|
||||||
|
ParentID *int `query:"parentID"`
|
||||||
|
|
||||||
|
MinAge *int `query:"minAge,omitempty"`
|
||||||
|
MaxAge *int `query:"maxAge,omitempty"`
|
||||||
|
Sex *entity.Sex `query:"sex,omitempty"`
|
||||||
|
Search *string `query:"search,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetPatientMapSummaryResponse struct {
|
||||||
|
Level entity.MapLevel `json:"level"`
|
||||||
|
Items map[uint][]entity.MapSummaryItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination ================================ Pagination =============================
|
||||||
|
type Pagination struct {
|
||||||
|
Limit int `query:"limit,omitempty"`
|
||||||
|
Offset int `query:"offset,omitempty"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PatientFilter struct {
|
||||||
|
DOBFrom *string // born after
|
||||||
|
DOBTo *string // born before
|
||||||
|
Sex *entity.Sex
|
||||||
|
|
||||||
|
City *int64
|
||||||
|
Province *int64
|
||||||
|
Country *int64
|
||||||
|
|
||||||
|
Search *string
|
||||||
|
|
||||||
|
Limit int
|
||||||
|
Offset int
|
||||||
|
}
|
||||||
|
|
||||||
|
type PatientMapFilter struct {
|
||||||
|
MinDOB *string
|
||||||
|
MaxDOB *string
|
||||||
|
Sex *entity.Sex
|
||||||
|
Search *string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package analytic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/patientapp/service/entity"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidProvinceID = errors.New("invalid province id")
|
||||||
|
ErrInvalidCountryID = errors.New("invalid country id")
|
||||||
|
ErrInvalidMapLevel = errors.New("invalid map level")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
GetPatients(ctx context.Context, f PatientFilter) ([]entity.Patient, error)
|
||||||
|
CountPatients(ctx context.Context, f PatientFilter) (int, error)
|
||||||
|
|
||||||
|
SummaryByCity(ctx context.Context, provinceID uint, f PatientMapFilter) (map[uint][]entity.MapSummaryItem, error)
|
||||||
|
SummaryByProvince(ctx context.Context, f PatientMapFilter) (map[uint][]entity.MapSummaryItem, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
repository Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPatientAnalyticService(repo Repository) Service {
|
||||||
|
return Service{
|
||||||
|
repository: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) List(ctx context.Context, req ListPatientAnalyticRequest) (PatientAnalyticResponse, error) {
|
||||||
|
|
||||||
|
limit, offset := normalizeLimitOffset(req.Pagination.Limit, req.Pagination.Offset)
|
||||||
|
|
||||||
|
// convert age range
|
||||||
|
dobFrom, dobTo := ageRangeToDOB(req.MinAge, req.MaxAge, time.Now())
|
||||||
|
|
||||||
|
filter := PatientFilter{
|
||||||
|
DOBFrom: dobFrom,
|
||||||
|
DOBTo: dobTo,
|
||||||
|
Sex: req.Sex,
|
||||||
|
City: req.City,
|
||||||
|
Province: req.Province,
|
||||||
|
Search: req.Search,
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := s.repository.GetPatients(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return PatientAnalyticResponse{}, fmt.Errorf("GetPatients: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
total, err := s.repository.CountPatients(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return PatientAnalyticResponse{}, fmt.Errorf("CountPatients: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mapping response
|
||||||
|
out := make([]PatientAnalyticItem, 0, len(items))
|
||||||
|
for _, value := range items {
|
||||||
|
out = append(out, ToPatientResponse(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return PatientAnalyticResponse{
|
||||||
|
Items: out,
|
||||||
|
Pagination: &Pagination{
|
||||||
|
Limit: limit,
|
||||||
|
Offset: offset,
|
||||||
|
},
|
||||||
|
Total: total,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) GetMapSummary(ctx context.Context, req GetPatientMapSummaryRequest) (GetPatientMapSummaryResponse, error) {
|
||||||
|
|
||||||
|
dobFrom, dobTo := ageRangeToDOB(req.MinAge, req.MaxAge, time.Now())
|
||||||
|
|
||||||
|
filter := PatientMapFilter{
|
||||||
|
MinDOB: dobFrom,
|
||||||
|
MaxDOB: dobTo,
|
||||||
|
Sex: req.Sex,
|
||||||
|
Search: req.Search,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch req.Level {
|
||||||
|
case entity.MapLevelCity:
|
||||||
|
if req.ParentID == nil || *req.ParentID <= 0 {
|
||||||
|
return GetPatientMapSummaryResponse{}, ErrInvalidProvinceID
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := s.repository.SummaryByCity(ctx, uint(*req.ParentID), filter)
|
||||||
|
if err != nil {
|
||||||
|
return GetPatientMapSummaryResponse{}, fmt.Errorf("SummaryByCity: %w", err)
|
||||||
|
}
|
||||||
|
return GetPatientMapSummaryResponse{Level: req.Level, Items: items}, nil
|
||||||
|
|
||||||
|
case entity.MapLevelProvince:
|
||||||
|
if req.ParentID == nil || *req.ParentID <= 0 {
|
||||||
|
return GetPatientMapSummaryResponse{}, ErrInvalidCountryID
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := s.repository.SummaryByProvince(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return GetPatientMapSummaryResponse{}, fmt.Errorf("SummaryByProvince: %w", err)
|
||||||
|
}
|
||||||
|
return GetPatientMapSummaryResponse{Level: req.Level, Items: items}, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return GetPatientMapSummaryResponse{}, ErrInvalidMapLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
ID uint
|
||||||
|
PostalCode string
|
||||||
|
Address string
|
||||||
|
Name string
|
||||||
|
Lat float64
|
||||||
|
Lon float64
|
||||||
|
CityID uint
|
||||||
|
ProvinceID uint
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddressAggregated struct {
|
||||||
|
Address Address
|
||||||
|
Province Province
|
||||||
|
City City
|
||||||
|
}
|
||||||
|
|
||||||
|
type Province struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type City struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
ProvinceID uint
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
type MapLevel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
MapLevelCity MapLevel = "city"
|
||||||
|
MapLevelProvince MapLevel = "province"
|
||||||
|
MapLevelCountry MapLevel = "country"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MapSummaryItem struct {
|
||||||
|
LocationID int64
|
||||||
|
Name string
|
||||||
|
Count int
|
||||||
|
CentroidLat float64
|
||||||
|
CentroidLng float64
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
type Patient struct {
|
||||||
|
ID int64
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
DateOfBirth string
|
||||||
|
Sex Sex
|
||||||
|
Phone string
|
||||||
|
Address Address
|
||||||
|
CaseStatus CaseStatus
|
||||||
|
ReferralSource ReferralSource
|
||||||
|
AssignedStaffId int64
|
||||||
|
StartDate string
|
||||||
|
EndDate string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sex ================================== Sex type ==========================================
|
||||||
|
type Sex string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SexUnknown Sex = "unknown"
|
||||||
|
SexMale Sex = "male"
|
||||||
|
SexFemale Sex = "female"
|
||||||
|
SexOther Sex = "other"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s Sex) SexValidation() bool {
|
||||||
|
switch s {
|
||||||
|
case SexUnknown, SexMale, SexFemale, SexOther:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaseStatus =================================== Case Status =======================================
|
||||||
|
type CaseStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Open CaseStatus = "open"
|
||||||
|
Close CaseStatus = "close"
|
||||||
|
InProgress CaseStatus = "inProgress"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s CaseStatus) CaseStatusValidation() bool {
|
||||||
|
switch s {
|
||||||
|
case Open, Close, InProgress:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferralSource =================================== Referral source =======================================
|
||||||
|
type ReferralSource string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Hospital ReferralSource = "hospital"
|
||||||
|
Community ReferralSource = "community"
|
||||||
|
Other ReferralSource = "other"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package date_parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseDate parses a date string in "YYYY-MM-DD" format and returns a time.Time object
|
||||||
|
func ParseDate(input string) (time.Time, error) {
|
||||||
|
const layout = "2006-01-02"
|
||||||
|
|
||||||
|
// Parse the input string
|
||||||
|
convertedDate, err := time.Parse(layout, input)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("invalid date format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedDate, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE `categories` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`name` VARCHAR(255) NOT NULL,
|
||||||
|
`slug` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_persian_ci;
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE IF EXISTS `categories`;
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE `products` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`name` VARCHAR(255) NOT NULL,
|
||||||
|
`slug` VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
`description` TEXT,
|
||||||
|
`price` DECIMAL(10, 2) NOT NULL,
|
||||||
|
`stock` INT DEFAULT 0,
|
||||||
|
`is_active` BOOLEAN DEFAULT TRUE,
|
||||||
|
`features` JSON DEFAULT NULL,
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`deleted_at` TIMESTAMP DEFAULT NULL,
|
||||||
|
FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE SET NULL
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_persian_ci;
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE IF EXISTS `products`;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE `category_products` (
|
||||||
|
`category_id` INT NOT NULL,
|
||||||
|
`product_id` INT NOT NULL,
|
||||||
|
PRIMARY KEY (`category_id`, `product_id`),
|
||||||
|
FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_persian_ci;
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE IF EXISTS `category_products`;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- +migrate Up
|
||||||
|
CREATE TABLE `product_images` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`product_id` INT NOT NULL,
|
||||||
|
`image_path` VARCHAR(255) NOT NULL,
|
||||||
|
`is_primary` BOOLEAN DEFAULT FALSE,
|
||||||
|
FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_persian_ci;
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE IF EXISTS `product_images`;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package category
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
Slug string
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package category
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package category
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package category
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package product
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Product struct {
|
||||||
|
ID uint
|
||||||
|
Name string
|
||||||
|
Slug string
|
||||||
|
Description string
|
||||||
|
Price float64
|
||||||
|
Stock int
|
||||||
|
IsActive bool
|
||||||
|
Features string
|
||||||
|
CreatedAt time.Time
|
||||||
|
DeletedAt sql.NullTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductImage struct {
|
||||||
|
ID uint
|
||||||
|
ProductID uint
|
||||||
|
ImagePath string
|
||||||
|
IsPrimary bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CategoryProduct struct {
|
||||||
|
CategoryID uint
|
||||||
|
ProductID uint
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package product
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package product
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package product
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +1,61 @@
|
||||||
package purchaseapp
|
package purchaseapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
purchaseHTTP "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http"
|
||||||
|
purchaseHandler "git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order"
|
||||||
|
purchaseMysql "git.gocasts.ir/ebhomengo/niki/purchaseapp/repository/mysql"
|
||||||
|
purchaseService "git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
Config Config
|
||||||
|
HTTPServer *purchaseHTTP.Server
|
||||||
|
purchaseService purchaseService.Service
|
||||||
|
PurchaseHandler *purchaseHandler.Handler
|
||||||
|
PurchaseRepo purchaseService.Repo
|
||||||
|
DB *mysql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetUp(ctx context.Context, config Config, DB mysql.DB) *Application {
|
||||||
|
|
||||||
|
//cfg := mysql.Config{
|
||||||
|
// Username: "niki",
|
||||||
|
// Password: "nikiappt0lk2o20",
|
||||||
|
// Port: 3306,
|
||||||
|
// Host: "localhost",
|
||||||
|
// DBName: "niki_db",
|
||||||
|
//}
|
||||||
|
db := mysql.New(config.Mysql)
|
||||||
|
defer func() {
|
||||||
|
if err := db.CloseStatements(); err != nil {
|
||||||
|
fmt.Printf("Error closing statements: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
orderRepo := purchaseMysql.New(db)
|
||||||
|
|
||||||
|
orderSvc := Service(orderRepo)
|
||||||
|
server := HTTPServer(orderSvc)
|
||||||
|
handler := purchaseHandler.New(orderSvc)
|
||||||
|
|
||||||
|
return &Application{
|
||||||
|
Config: Config{},
|
||||||
|
HTTPServer: server,
|
||||||
|
purchaseService: orderSvc,
|
||||||
|
PurchaseHandler: handler,
|
||||||
|
PurchaseRepo: orderRepo,
|
||||||
|
DB: &DB,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func HTTPServer(orderSvc purchaseService.Service) *purchaseHTTP.Server {
|
||||||
|
return purchaseHTTP.New(orderSvc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Service(orderRepo *purchaseMysql.DB) purchaseService.Service {
|
||||||
|
return purchaseService.New(orderRepo)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
package purchaseapp
|
package purchaseapp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
Mysql mysql.Config `koanf:"mariadb"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package invoice
|
|
||||||
|
|
||||||
type Handler struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *Handler {
|
|
||||||
return &Handler{}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package invoice
|
|
||||||
|
|
||||||
import "github.com/labstack/echo/v4"
|
|
||||||
|
|
||||||
func (h Handler) SetRoutes(e *echo.Echo) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,68 @@
|
||||||
package order
|
package order
|
||||||
|
|
||||||
type Handler struct{}
|
import (
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func New() *Handler {
|
type Handler struct {
|
||||||
return &Handler{}
|
orderSvc *order.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(orderSvc order.Service) *Handler {
|
||||||
|
return &Handler{orderSvc: &orderSvc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) CreateOrderHandler(c echo.Context) error {
|
||||||
|
var req order.CreateOrderRequest
|
||||||
|
if err := c.Bind(&req); err != nil {
|
||||||
|
msg, code := getErrorDataFromRichError(err)
|
||||||
|
return echo.NewHTTPError(code, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
orderItems := req.OrderItems
|
||||||
|
order := entity.Order{
|
||||||
|
ID: 0,
|
||||||
|
UserID: req.UserID,
|
||||||
|
TotalAmount: req.TotalAmount,
|
||||||
|
TotalDiscount: req.TotalDiscount,
|
||||||
|
ShippingID: req.ShippingID,
|
||||||
|
PaymentMethod: req.PaymentMethod,
|
||||||
|
ProcessStatus: entity.WaitingToPay,
|
||||||
|
PaymentStatus: entity.UnPaid,
|
||||||
|
Address: req.Address,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
resp, lErr := h.orderSvc.CreateOrder(order, orderItems)
|
||||||
|
|
||||||
|
if lErr != nil {
|
||||||
|
msg, code := getErrorDataFromRichError(lErr)
|
||||||
|
return echo.NewHTTPError(code, msg)
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getErrorDataFromRichError(err error) (message string, code int) {
|
||||||
|
switch err.(type) {
|
||||||
|
case richerror.RichError:
|
||||||
|
re := err.(richerror.RichError)
|
||||||
|
|
||||||
|
return re.Message(), mapKindToCode(re.Kind())
|
||||||
|
default:
|
||||||
|
return err.Error(), http.StatusBadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapKindToCode(kind richerror.Kind) int {
|
||||||
|
switch kind {
|
||||||
|
case richerror.KindInvalid:
|
||||||
|
return http.StatusUnprocessableEntity
|
||||||
|
default:
|
||||||
|
return http.StatusBadRequest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ import "github.com/labstack/echo/v4"
|
||||||
|
|
||||||
func (h Handler) SetRoutes(e *echo.Echo) {
|
func (h Handler) SetRoutes(e *echo.Echo) {
|
||||||
|
|
||||||
|
e.POST("/order/create", h.CreateOrderHandler)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,32 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
httpserver "git.gocasts.ir/ebhomengo/niki/delivery/http_server"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/invoice"
|
|
||||||
"git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order"
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/delivery/http/order"
|
||||||
|
orderService "git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
HTTPServer *httpserver.Server
|
OrderHandler *order.Handler
|
||||||
OrderHandler *order.Handler
|
|
||||||
InvoiceHandler *invoice.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(httpserver *httpserver.Server) *Server {
|
func New(orderSvc orderService.Service) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
HTTPServer: httpserver,
|
OrderHandler: order.New(orderSvc),
|
||||||
OrderHandler: order.New(),
|
|
||||||
InvoiceHandler: invoice.New(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Serve() {
|
func (s *Server) Serve() {
|
||||||
s.RegisterRoutes()
|
e := echo.New()
|
||||||
|
e.Use(middleware.RequestLogger())
|
||||||
|
e.GET("/purchase/health-check", s.healthCheck)
|
||||||
|
|
||||||
|
s.OrderHandler.SetRoutes(e)
|
||||||
|
|
||||||
|
if err := e.Start(":8088"); err != nil {
|
||||||
|
e.Logger.Error("failed to start server", "error", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Stop() {}
|
func (s *Server) Stop() {}
|
||||||
|
|
||||||
func (s *Server) RegisterRoutes() {
|
|
||||||
s.HTTPServer.Router.GET("/purchase/health-check", s.healthCheck)
|
|
||||||
s.OrderHandler.SetRoutes(s.HTTPServer.Router)
|
|
||||||
s.InvoiceHandler.SetRoutes(s.HTTPServer.Router)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
package entity
|
|
||||||
|
|
||||||
type Invoice struct {
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,59 @@
|
||||||
package entity
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type Order struct {
|
type Order struct {
|
||||||
|
ID types.ID
|
||||||
|
UserID types.ID
|
||||||
|
TotalAmount types.Price
|
||||||
|
TotalDiscount types.Price
|
||||||
|
ShippingID types.ID
|
||||||
|
PaymentMethod PaymentMethod
|
||||||
|
ProcessStatus ProcessStatus
|
||||||
|
PaymentStatus PaymentStatus
|
||||||
|
Address string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrderItem struct {
|
||||||
|
ID types.ID
|
||||||
|
ProductID types.ID
|
||||||
|
Price types.Price
|
||||||
|
Quantity types.Count
|
||||||
|
PriceWithDiscount types.Price
|
||||||
|
OrderID types.ID
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Online PaymentMethod = "online"
|
||||||
|
Wallet = "wallet"
|
||||||
|
Cart = "cart"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProcessStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
WaitingToPay ProcessStatus = "waiting-to-pay"
|
||||||
|
processing = "processing"
|
||||||
|
accepted = "accepted"
|
||||||
|
preparing = "preparing"
|
||||||
|
prepared = "prepared"
|
||||||
|
givenToPost = "given-to-post"
|
||||||
|
delivered = "delivered"
|
||||||
|
cancelled = "cancelled"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Paid PaymentStatus = "paid"
|
||||||
|
UnPaid = "unpaid"
|
||||||
|
Cancelled = "cancelled"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
-- +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 `orders` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`user_id` INT NOT NULL,
|
||||||
|
`address` TEXT,
|
||||||
|
`shipping_id` INT NOT NULL,
|
||||||
|
`payment_method` ENUM('online', 'wallet', 'cart') DEFAULT 'online',
|
||||||
|
`payment_status` ENUM('unpaid', 'paid', 'cancelled') DEFAULT 'unpaid',
|
||||||
|
`process_status` ENUM('waiting-to-pay', 'processing', 'accepted', 'preparing', 'prepared', 'given-to-post', 'delivered', 'cancelled') DEFAULT 'waiting-to-pay',
|
||||||
|
`total_amount` INT NOT NULL,
|
||||||
|
`total_discount` INT NULL,
|
||||||
|
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
-- FOREIGN KEY (`user_id`) REFERENCES `users`(`id`)
|
||||||
|
-- FOREIGN KEY (`shipping_id`) REFERENCES `shippings`(`id`)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE `orders`;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- +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 `order_items` (
|
||||||
|
`id` INT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
`order_id` INT NOT NULL,
|
||||||
|
`product_id` INT NOT NULL,
|
||||||
|
`quantity` INT DEFAULT 1,
|
||||||
|
`price` INT NOT NULL,
|
||||||
|
`price_with_discount` INT NULL,
|
||||||
|
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`)
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
-- +migrate Down
|
||||||
|
DROP TABLE `order_items`;
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
|
||||||
|
"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}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (types.ID, error) {
|
||||||
|
|
||||||
|
const Op = "repository.mysql.order.createorder"
|
||||||
|
tx, err := d.conn.Conn().Begin()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
query := "insert into orders(user_id, address, shipping_id," +
|
||||||
|
" payment_method, payment_status, process_status," +
|
||||||
|
" total_amount, total_discount) values (?, ?, ?, ?, ?, ?, ?, ?);"
|
||||||
|
res, oErr := tx.Exec(query, order.UserID, order.Address, order.ShippingID,
|
||||||
|
order.PaymentMethod, order.PaymentStatus, order.ProcessStatus,
|
||||||
|
order.TotalAmount, order.TotalDiscount)
|
||||||
|
|
||||||
|
if oErr != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(oErr)
|
||||||
|
}
|
||||||
|
orderID, insertIDErr := res.LastInsertId()
|
||||||
|
if insertIDErr != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(insertIDErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
orderItemQuery := "insert into order_items(order_id, product_id, quantity, price, price_with_discount) values(?, ?, ?, ?, ?);"
|
||||||
|
for _, item := range orderItems {
|
||||||
|
_, iErr := tx.Exec(orderItemQuery, orderID, item.ProductID, item.Quantity, item.Price, item.PriceWithDiscount)
|
||||||
|
if iErr != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(iErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return 0, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.ID(orderID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error) {
|
||||||
|
const Op = "repository.mysql.order.update-order-process-status"
|
||||||
|
_, err := d.conn.Conn().Exec("update orders set process_status=? where id=?;", status, orderID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package invoice
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
package invoice
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package invoice
|
|
||||||
|
|
@ -1 +1,20 @@
|
||||||
package order
|
package order
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateOrderRequest struct {
|
||||||
|
UserID types.ID `json:"user_id"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
ShippingID types.ID `json:"shipping_id"`
|
||||||
|
PaymentMethod entity.PaymentMethod `json:"payment_method"`
|
||||||
|
TotalAmount types.Price `json:"total_amount"`
|
||||||
|
TotalDiscount types.Price `json:"total_discount"`
|
||||||
|
OrderItems []entity.OrderItem `json:"order_items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateOrderResponse struct {
|
||||||
|
OrderID types.ID
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,42 @@
|
||||||
package order
|
package order
|
||||||
|
|
||||||
|
import (
|
||||||
|
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/purchaseapp/entity"
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/types"
|
||||||
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
|
repo Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo interface {
|
||||||
|
CreateOrder(order entity.Order, orderItems []entity.OrderItem) (types.ID, error)
|
||||||
|
UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(orderRepo Repo) Service {
|
||||||
|
return Service{repo: orderRepo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) CreateOrder(order entity.Order, orderItems []entity.OrderItem) (CreateOrderResponse, error) {
|
||||||
|
const Op = "purchaseapp.service.CreateOrder"
|
||||||
|
orderID, err := s.repo.CreateOrder(order, orderItems)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return CreateOrderResponse{}, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateOrderResponse{OrderID: orderID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Service) UpdateOrderProcessStatus(orderID types.ID, status string) (bool, error) {
|
||||||
|
|
||||||
|
const Op = "purchaseapp.service.UpdateOrderProcessStatus"
|
||||||
|
_, err := s.repo.UpdateOrderProcessStatus(orderID, status)
|
||||||
|
if err != nil {
|
||||||
|
return false, richerror.New(Op).WithErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println(" Staffapp Server Starting...")
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
fmt.Fprintf(w, "Staffapp OK!")
|
|
||||||
})
|
|
||||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,64 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.gocasts.ir/ebhomengo/niki/staffapp/service"
|
||||||
|
)
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
conn *mysql.DB
|
staffs []service.Staff
|
||||||
|
nextID int
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(conn *mysql.DB) *DB {
|
func New() service.StaffRepository {
|
||||||
return &DB{
|
return &DB{
|
||||||
conn: conn,
|
staffs: []service.Staff{},
|
||||||
|
nextID: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DB) Create(staff service.Staff) (service.Staff, error) {
|
||||||
|
staff.ID = d.nextID
|
||||||
|
d.staffs = append(d.staffs, staff)
|
||||||
|
d.nextID++
|
||||||
|
return staff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) Get(id int) (*service.Staff, error) {
|
||||||
|
for _, v := range d.staffs {
|
||||||
|
if id == v.ID {
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("The user not found with id = %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DB) List() ([]service.Staff, error) {
|
||||||
|
return d.staffs, nil
|
||||||
|
}
|
||||||
|
func (d *DB) Remove(id int) error {
|
||||||
|
for _, v := range d.staffs {
|
||||||
|
if id == v.ID {
|
||||||
|
d.staffs = append(d.staffs[:id], d.staffs[id+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return fmt.Errorf("User with ID: %d notfound", id)
|
||||||
|
}
|
||||||
|
func (d *DB) Update(id int, name, lastName, phoneNumber string) (*service.Staff, error) {
|
||||||
|
for _, v := range d.staffs {
|
||||||
|
if id == v.ID {
|
||||||
|
v.Name = name
|
||||||
|
v.LastName = lastName
|
||||||
|
v.PhoneNumber = phoneNumber
|
||||||
|
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("User with this Id(%d)couldn't found for updating", id)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
type Staff struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
LastName string
|
||||||
|
PhoneNumber string
|
||||||
|
}
|
||||||
|
type StaffRepository interface {
|
||||||
|
Create(staff Staff) (Staff, error)
|
||||||
|
Get(id int) (*Staff, error)
|
||||||
|
List() ([]Staff, error)
|
||||||
|
Remove(id int) error
|
||||||
|
Update(id int, name, lastName, phoneNumber string) (*Staff, error)
|
||||||
|
}
|
||||||
|
|
@ -1 +1,44 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StaffService struct {
|
||||||
|
repo StaffRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStaffService(repo StaffRepository) *StaffService {
|
||||||
|
return &StaffService{
|
||||||
|
repo: repo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaffService) RegisterStaff(name, lastname, phone string) (Staff, error) {
|
||||||
|
newStaff := Staff{
|
||||||
|
Name: name,
|
||||||
|
LastName: lastname,
|
||||||
|
PhoneNumber: phone,
|
||||||
|
}
|
||||||
|
createdStaff, err := s.repo.Create(newStaff)
|
||||||
|
if err != nil {
|
||||||
|
return Staff{}, fmt.Errorf("Error in registring staff%w", err)
|
||||||
|
}
|
||||||
|
return createdStaff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaffService) Get(id int) (*Staff, error) {
|
||||||
|
return s.repo.Get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaffService) List() ([]Staff, error) {
|
||||||
|
return s.repo.List()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaffService) Remove(id int) error {
|
||||||
|
return s.repo.Remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StaffService) Update(id int, name, lastName, phoneNumber string) (*Staff, error) {
|
||||||
|
return s.repo.Update(id, name, lastName, phoneNumber)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package service
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
type Count uint32
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue