Merge pull request #8 from anxi0uz/feature/back-08

added manager, driver, order, route, vehicle, warehouse endpoints,mod…
This commit is contained in:
2026-03-23 18:29:15 +05:00
committed by GitHub
22 changed files with 2463 additions and 111 deletions

1
go.mod
View File

@@ -25,6 +25,7 @@ require (
) )
require ( require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect

6
go.sum
View File

@@ -1,3 +1,7 @@
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -50,6 +54,7 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
@@ -90,6 +95,7 @@ github.com/samber/slog-chi v1.19.0 h1:fl4qH5Hhk7feHtyp4CxJUt7U1TqjPrZ1uueDW9D+Cp
github.com/samber/slog-chi v1.19.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU= github.com/samber/slog-chi v1.19.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU=
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
package handler
import "net/http"
func (s *Server) ListDrivers(w http.ResponseWriter, r *http.Request) {}
func (s *Server) CreateDriver(w http.ResponseWriter, r *http.Request) {}
func (s *Server) GetDriver(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) UpdateDriver(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) DeleteDriver(w http.ResponseWriter, r *http.Request, slug string) {}

View File

@@ -0,0 +1,11 @@
package handler
import "net/http"
func (s *Server) ListManagers(w http.ResponseWriter, r *http.Request) {}
func (s *Server) CreateManager(w http.ResponseWriter, r *http.Request) {}
func (s *Server) GetManager(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) DeleteManager(w http.ResponseWriter, r *http.Request, slug string) {}

17
internal/handler/order.go Normal file
View File

@@ -0,0 +1,17 @@
package handler
import (
"net/http"
openapi_types "github.com/oapi-codegen/runtime/types"
)
func (s *Server) ListOrders(w http.ResponseWriter, r *http.Request) {}
func (s *Server) CreateOrder(w http.ResponseWriter, r *http.Request) {}
func (s *Server) GetOrder(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
func (s *Server) CancelOrder(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
func (s *Server) UpdateOrderStatus(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}

11
internal/handler/route.go Normal file
View File

@@ -0,0 +1,11 @@
package handler
import (
"net/http"
openapi_types "github.com/oapi-codegen/runtime/types"
)
func (s *Server) GetRoute(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}
func (s *Server) RouteWebSocket(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) {}

View File

@@ -47,7 +47,7 @@ func (s *Server) AuthLogin(w http.ResponseWriter, r *http.Request) {
} }
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil {
slog.WarnContext(ctx, "Failed login to account with", slog.String("email:", string(req.Email)), slog.String("password from request", req.Password)) slog.WarnContext(ctx, "Failed login to account with", slog.String("email:", string(req.Email)))
s.JSON(w, r, http.StatusBadRequest, "Неверный пароль", "error") s.JSON(w, r, http.StatusBadRequest, "Неверный пароль", "error")
return return
} }
@@ -207,7 +207,7 @@ func (s *Server) DeleteMe(w http.ResponseWriter, r *http.Request) {
} }
jwt := r.Header.Get("Authorization") jwt := r.Header.Get("Authorization")
tokenKey := "access_token" + jwt tokenKey := "access_token:" + jwt
if err := s.Redis.Del(ctx, tokenKey).Err(); err != nil { if err := s.Redis.Del(ctx, tokenKey).Err(); err != nil {
slog.ErrorContext(ctx, "Error while deleting access token from redis", slog.String("token", jwt)) slog.ErrorContext(ctx, "Error while deleting access token from redis", slog.String("token", jwt))
s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error")

View File

@@ -0,0 +1,13 @@
package handler
import "net/http"
func (s *Server) ListVehicles(w http.ResponseWriter, r *http.Request) {}
func (s *Server) CreateVehicle(w http.ResponseWriter, r *http.Request) {}
func (s *Server) GetVehicle(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) UpdateVehicle(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) DeleteVehicle(w http.ResponseWriter, r *http.Request, slug string) {}

View File

@@ -0,0 +1,13 @@
package handler
import "net/http"
func (s *Server) ListWarehouses(w http.ResponseWriter, r *http.Request) {}
func (s *Server) CreateWarehouse(w http.ResponseWriter, r *http.Request) {}
func (s *Server) GetWarehouse(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) UpdateWarehouse(w http.ResponseWriter, r *http.Request, slug string) {}
func (s *Server) DeleteWarehouse(w http.ResponseWriter, r *http.Request, slug string) {}

18
internal/models/driver.go Normal file
View File

@@ -0,0 +1,18 @@
package models
import (
"time"
"github.com/google/uuid"
)
type Driver struct {
ID uuid.UUID `db:"id"`
UserID uuid.UUID `db:"user_id"`
VehicleID *uuid.UUID `db:"vehicle_id"`
LicenseNumber string `db:"license_number"`
LicenseExpiry time.Time `db:"license_expiry"`
Rating float64 `db:"rating"`
Slug string `db:"slug"`
Status string `db:"status"` // available, on_route, off_duty
}

View File

@@ -0,0 +1,10 @@
package models
import "github.com/google/uuid"
type Manager struct {
ID uuid.UUID `db:"id"`
UserID uuid.UUID `db:"user_id"`
WarehouseID *uuid.UUID `db:"warehouse_id"`
Slug string `db:"slug"`
}

25
internal/models/order.go Normal file
View File

@@ -0,0 +1,25 @@
package models
import (
"time"
"github.com/google/uuid"
)
type Order struct {
ID uuid.UUID `db:"id"`
CreatedByID *uuid.UUID `db:"created_by_id"`
DriverID *uuid.UUID `db:"driver_id"`
ManagerID *uuid.UUID `db:"manager_id"`
OriginWarehouseID *uuid.UUID `db:"origin_warehouse_id"`
OriginAddress string `db:"origin_address"`
DestinationAddress string `db:"destination_address"`
CargoDescription string `db:"cargo_description"`
WeightKg float64 `db:"weight_kg"`
VolumeM3 float64 `db:"volume_m3"`
Status string `db:"status"` // pending, assigned, in_transit, delivered, cancelled
TotalPrice float64 `db:"total_price"`
CreatedAt time.Time `db:"created_at"`
AssignedAt *time.Time `db:"assigned_at"`
DeliveredAt *time.Time `db:"delivered_at"`
}

33
internal/models/route.go Normal file
View File

@@ -0,0 +1,33 @@
package models
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
// Coordinate represents a [longitude, latitude] pair — ORS array format.
type Coordinate [2]float64
type Route struct {
ID uuid.UUID `db:"id"`
OrderID uuid.UUID `db:"order_id"`
DriverID *uuid.UUID `db:"driver_id"`
Coordinates json.RawMessage `db:"coordinates"` // JSONB: [][2]float64
CurrentIndex int `db:"current_index"`
StartedAt *time.Time `db:"started_at"`
FinishedAt *time.Time `db:"finished_at"`
DistanceKm float64 `db:"distance_km"`
DurationSec int `db:"duration_sec"`
Status string `db:"status"` // pending, active, finished
}
// ParseCoordinates unmarshals the raw JSONB coordinates into a typed slice.
func (r *Route) ParseCoordinates() ([]Coordinate, error) {
var coords []Coordinate
if err := json.Unmarshal(r.Coordinates, &coords); err != nil {
return nil, err
}
return coords, nil
}

View File

@@ -0,0 +1,15 @@
package models
import "github.com/google/uuid"
type Vehicle struct {
ID uuid.UUID `db:"id"`
PlateNumber string `db:"plate_number"`
Brand string `db:"brand"`
Model string `db:"model"`
Year int `db:"year"`
CapacityKg float64 `db:"capacity_kg"`
CapacityM3 float64 `db:"capacity_m3"`
Status string `db:"status"` // available, in_transit, maintenance
Slug string `db:"slug"`
}

View File

@@ -0,0 +1,19 @@
package models
import (
"time"
"github.com/google/uuid"
)
type Warehouse struct {
ID uuid.UUID `db:"id"`
Slug string `db:"slug"`
Name string `db:"name"`
Address string `db:"address"`
City string `db:"city"`
Latitude float64 `db:"latitude"`
Longitude float64 `db:"longitude"`
Status string `db:"status"` // active, inactive
CreatedAt time.Time `db:"created_at"`
}

View File

@@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS users (
updated_at TIMESTAMPTZ, updated_at TIMESTAMPTZ,
last_login_at TIMESTAMPTZ last_login_at TIMESTAMPTZ
); );
-- +goose StatementEnd -- +goose StatementEnd
-- +goose Down -- +goose Down

View File

@@ -1,9 +1,25 @@
-- +goose Up -- +goose Up
-- +goose StatementBegin -- +goose StatementBegin
SELECT 'up SQL query'; CREATE TABLE IF NOT EXISTS orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
created_by_id UUID REFERENCES users(id) ON DELETE SET NULL,
driver_id UUID REFERENCES drivers(id) ON DELETE SET NULL,
manager_id UUID REFERENCES managers(id) ON DELETE SET NULL,
origin_warehouse_id UUID REFERENCES warehouses(id) ON DELETE SET NULL,
origin_address VARCHAR(255),
destination_address VARCHAR(255) NOT NULL,
cargo_description TEXT,
weight_kg NUMERIC(10,2),
volume_m3 NUMERIC(10,2),
status VARCHAR(20) DEFAULT 'pending',
total_price NUMERIC(12,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
assigned_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ
);
-- +goose StatementEnd -- +goose StatementEnd
-- +goose Down -- +goose Down
-- +goose StatementBegin -- +goose StatementBegin
SELECT 'down SQL query'; DROP TABLE IF EXISTS orders;
-- +goose StatementEnd -- +goose StatementEnd

View File

@@ -0,0 +1,14 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS managers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID UNIQUE REFERENCES users(id) ON DELETE CASCADE,
warehouse_id UUID REFERENCES warehouses(id) ON DELETE SET NULL,
slug VARCHAR(120) UNIQUE NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS managers;
-- +goose StatementEnd

View File

@@ -0,0 +1,19 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS warehouses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(120) UNIQUE NOT NULL,
name VARCHAR(150) NOT NULL,
address VARCHAR(255) NOT NULL,
city VARCHAR(100),
latitude NUMERIC(9,6),
longitude NUMERIC(9,6),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS warehouses;
-- +goose StatementEnd

View File

@@ -0,0 +1,20 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE IF NOT EXISTS routes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID UNIQUE REFERENCES orders(id) ON DELETE CASCADE,
driver_id UUID REFERENCES drivers(id) ON DELETE SET NULL,
coordinates JSONB,
current_index INTEGER DEFAULT 0,
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
distance_km NUMERIC(10,2),
duration_sec INTEGER,
status VARCHAR(20) DEFAULT 'pending'
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS routes;
-- +goose StatementEnd