Merge branch 'develop' into feat-benefactorcmd-pkg

This commit is contained in:
zahra-sh 2026-04-10 09:35:54 -07:00
commit 0cc151ba0c
239 changed files with 31138 additions and 45718 deletions

View File

@ -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)
}

View File

@ -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.`,
}

View File

@ -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)
}

13
cmd/productapp/main.go Normal file
View File

@ -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)
}
}

View File

@ -0,0 +1 @@
package command

View File

@ -0,0 +1 @@
package command

View File

@ -0,0 +1 @@
package command

63
cmd/purchaseapp/main.go Normal file
View File

@ -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)
}

View File

@ -1,15 +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() {
fmt.Println(" Staffapp Server Starting...")
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))
}

View File

@ -15,9 +15,10 @@ const (
defaultPrefix = "EB_"
defaultDelimiter = "."
defaultSeparator = "__"
defaultYamlFilePath = "config.yml"
)
var defaultYamlFilePath = "config.yml"
var c Config
type Option struct {

View File

@ -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)
}
}

View File

@ -0,0 +1,8 @@
package entity
type Channel struct {
ID int8
Type NotificationType
Provider string
Config string
}

View File

@ -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"
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,7 @@
package service
import "git.gocasts.ir/ebhomengo/niki/domain/notification/entity"
type sender interface {
Send(notification entity.Notification) error
}

View File

@ -1 +0,0 @@
package donate

View File

@ -1,5 +0,0 @@
package donateapp
type Config struct{
}

View File

@ -1,8 +0,0 @@
package donate_server
type Handler struct {
}
func NewHandler() Handler {
return Handler{}
}

View File

@ -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`;

View File

@ -1 +0,0 @@
package mysql

View File

@ -1 +0,0 @@
package service

View File

@ -1 +0,0 @@
package service

View File

@ -1,4 +1,4 @@
package doanteApp
package donate_app
import "net/http"
@ -6,3 +6,5 @@ type Application struct {
Config Config
HTTPServer *http.Server
}

17
donate_app/config.go Normal file
View File

@ -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{
}

View File

@ -0,0 +1,5 @@
package donate_server
type Handler struct{}

4
donate_app/pkg/types.go Normal file
View File

@ -0,0 +1,4 @@
// --- Type Aliases ---
package pkg
type ID uint64

View File

@ -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`;

View File

@ -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`;

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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
View File

@ -21,7 +21,7 @@ require (
github.com/stretchr/testify v1.11.1
github.com/swaggo/echo-swagger v1.5.2
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
)
@ -82,13 +82,13 @@ require (
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/mod v0.30.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.35.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.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect

28
go.sum
View File

@ -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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
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/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=
@ -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.3.0/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.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
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-20180826012351-8a410e7b638d/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
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.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
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-20190226205417-e64efc72b421/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-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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
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-20180830151530-49385e6e1522/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-20220715151400-c0bba94af5f8/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.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
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/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=
@ -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.5/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.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
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.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -43,7 +43,7 @@ func Config() config.Config {
}
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()
if *migrate {
migrator.New(migrator.Config{

View File

@ -1 +1,37 @@
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()
}

26
patientapp/cmd/main.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"
)

View File

@ -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
}

View File

@ -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`;

View File

@ -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`;

View File

@ -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`;

View File

@ -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`;

View File

@ -0,0 +1,10 @@
package category
import "time"
type Category struct {
ID uint
Name string
Slug string
CreatedAt time.Time
}

View File

@ -0,0 +1 @@
package category

View File

@ -0,0 +1 @@
package category

View File

@ -0,0 +1 @@
package category

View File

@ -1 +0,0 @@
package service

View File

@ -1 +0,0 @@
package service

View File

@ -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
}

View File

@ -0,0 +1 @@
package product

View File

@ -0,0 +1 @@
package product

View File

@ -0,0 +1 @@
package product

View File

@ -1 +0,0 @@
package service

View File

@ -1 +0,0 @@
package service

View File

@ -1 +1,61 @@
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)
}

View File

@ -1,4 +1,9 @@
package purchaseapp
import (
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)
type Config struct {
Mysql mysql.Config `koanf:"mariadb"`
}

View File

@ -1,8 +0,0 @@
package invoice
type Handler struct {
}
func New() *Handler {
return &Handler{}
}

View File

@ -1,7 +0,0 @@
package invoice
import "github.com/labstack/echo/v4"
func (h Handler) SetRoutes(e *echo.Echo) {
}

View File

@ -1,7 +1,68 @@
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 {
return &Handler{}
type Handler struct {
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
}
}

View File

@ -4,4 +4,5 @@ import "github.com/labstack/echo/v4"
func (h Handler) SetRoutes(e *echo.Echo) {
e.POST("/order/create", h.CreateOrderHandler)
}

View File

@ -1,33 +1,32 @@
package http
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"
orderService "git.gocasts.ir/ebhomengo/niki/purchaseapp/service/order"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Server struct {
HTTPServer *httpserver.Server
OrderHandler *order.Handler
InvoiceHandler *invoice.Handler
}
func New(httpserver *httpserver.Server) *Server {
func New(orderSvc orderService.Service) *Server {
return &Server{
HTTPServer: httpserver,
OrderHandler: order.New(),
InvoiceHandler: invoice.New(),
OrderHandler: order.New(orderSvc),
}
}
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) RegisterRoutes() {
s.HTTPServer.Router.GET("/purchase/health-check", s.healthCheck)
s.OrderHandler.SetRoutes(s.HTTPServer.Router)
s.InvoiceHandler.SetRoutes(s.HTTPServer.Router)
}

View File

@ -1,4 +0,0 @@
package entity
type Invoice struct {
}

View File

@ -1,4 +1,59 @@
package entity
import (
"git.gocasts.ir/ebhomengo/niki/types"
"time"
)
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"
)

View File

@ -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`;

View File

@ -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`;

View File

@ -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
}

View File

@ -1 +0,0 @@
package invoice

View File

@ -1,4 +0,0 @@
package invoice
type Service struct {
}

View File

@ -1 +0,0 @@
package invoice

View File

@ -1 +1,20 @@
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
}

View File

@ -1,4 +1,42 @@
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 {
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
}

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"sync"
"time"

View File

@ -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))
}

View File

@ -1,13 +1,64 @@
package database
import "git.gocasts.ir/ebhomengo/niki/repository/mysql"
import (
"fmt"
"git.gocasts.ir/ebhomengo/niki/staffapp/service"
)
type DB struct {
conn *mysql.DB
staffs []service.Staff
nextID int
}
func New(conn *mysql.DB) *DB {
func New() service.StaffRepository {
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)
}

View File

@ -1 +0,0 @@
package service

View File

@ -1 +0,0 @@
package service

View File

@ -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)
}

View File

@ -1 +1,44 @@
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)
}

View File

@ -1 +0,0 @@
package service

3
types/count.go Normal file
View File

@ -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