1 Commits

Author SHA1 Message Date
0b6b92876e Merge pull request 'dev' (#1) from dev into main
Some checks failed
Build and Push Docker Image / build_and_push (push) Failing after 3m0s
Reviewed-on: #1
2025-10-08 08:45:05 +00:00
6 changed files with 33 additions and 310 deletions

View File

@@ -1,99 +1,12 @@
# COOPGO Fleets
COOPGO Fleets is a vehicle fleet management microservice providing vehicle and booking management capabilities.
COOPGO Fleets is a vehicle management and booking service.
## Features
It provides :
- gRPC API for vehicle and booking management
- Vehicle availability tracking with booking integration
- Flexible data schema for custom vehicle and booking attributes
- Soft delete for bookings with metadata preservation
- Multi-namespace support for fleet isolation
- Vehicle filtering by type, administrator, and availability
- a gRPC API to manage fleets of vehicles and bookings
- availability information on véhicles
- a flexible data schema to push any information on vehicles or bookings
## Architecture
It is not a full featured booking platform but just a microservice intended to be used as part of a bigger platform, like the COOPGO Technical Platform. It doesn't provide any king of booking UI or management interface.
This microservice is designed to be part of the COOPGO Technical Platform. It does not provide a UI or management interface.
```
├── grpcapi/ # gRPC server and protocol definitions
├── handlers/ # Business logic
└── storage/ # Database access layer (MongoDB, PostgreSQL)
```
## Configuration
Configuration is managed via Viper. Create a `config.yaml` file or use environment variables.
### Default configuration
```yaml
name: "COOPGO Fleets"
dev_env: false
storage:
db:
type: mongodb # or "psql"
mongodb:
host: localhost
port: "27017"
db_name: coopgo_platform
collections:
vehicles: fleet_vehicles
bookings: fleet_bookings
services:
grpc:
enable: true
port: 8093
```
### Environment variables
Use underscore-separated uppercase names: `STORAGE_DB_TYPE`, `SERVICES_GRPC_PORT`, etc.
## Storage backends
- **MongoDB** (default)
- **PostgreSQL** (with Atlas migrations)
## Build
```bash
go build
```
## Docker
```bash
docker build \
--build-arg ACCESS_TOKEN_USR=<user> \
--build-arg ACCESS_TOKEN_PWD=<password> \
-t coopgo-fleets .
```
## gRPC API
### Vehicles
| Method | Description |
|--------|-------------|
| `AddVehicle` | Create a new vehicle |
| `GetVehicle` | Get a vehicle by ID (includes bookings) |
| `GetVehicles` | List vehicles with filters |
| `UpdateVehicle` | Update vehicle data |
### Bookings
| Method | Description |
|--------|-------------|
| `CreateBooking` | Create a booking |
| `GetBooking` | Get a booking by ID |
| `GetBookings` | List all bookings |
| `GetDriverBookings` | Get bookings for a driver |
| `UpdateBooking` | Update a booking |
| `DeleteBooking` | Soft delete a booking |
## License
See [LICENSE.md](LICENSE.md)

View File

@@ -11,16 +11,14 @@ func ReadConfig() (*viper.Viper, error) {
"name": "COOPGO Fleets",
"dev_env": false,
"storage": map[string]any{
"db": map[string]any{
"type": "mongodb",
"mongodb": map[string]any{
"host": "localhost",
"port": "27017",
"db_name": "coopgo_platform",
"collections": map[string]any{
"vehicles": "vehicles",
"bookings": "bookings",
},
"vehicles": "fleet_vehicles",
"bookings": "fleet_bookings",
},
},
},

View File

@@ -21,7 +21,6 @@ func (v Booking) ToStorageType() storage.Booking {
Unavailableto: v.Unavailableto.AsTime(),
Data: map[string]any{},
Deleted: v.Deleted,
ManualStatus: v.ManualStatus,
}
for k, d := range v.Data.GetFields() {
@@ -39,19 +38,6 @@ func (v Booking) ToStorageType() storage.Booking {
booking.Vehicle = v.Vehicle.ToStorageType()
}
for _, entry := range v.StatusHistory {
booking.StatusHistory = append(booking.StatusHistory, storage.StatusHistoryEntry{
FromStatus: entry.FromStatus,
ToStatus: entry.ToStatus,
UserID: entry.UserId,
UserName: entry.UserName,
GroupID: entry.GroupId,
GroupName: entry.GroupName,
Date: entry.Date.AsTime(),
Comment: entry.Comment,
})
}
return booking
}
@@ -77,20 +63,6 @@ func BookingFromStorageType(booking *storage.Booking) (*Booking, error) {
Unavailableto: timestamppb.New(booking.Unavailableto),
Data: data,
Deleted: booking.Deleted,
ManualStatus: booking.ManualStatus,
}
for _, entry := range booking.StatusHistory {
result.StatusHistory = append(result.StatusHistory, &StatusHistoryEntry{
FromStatus: entry.FromStatus,
ToStatus: entry.ToStatus,
UserId: entry.UserID,
UserName: entry.UserName,
GroupId: entry.GroupID,
GroupName: entry.GroupName,
Date: timestamppb.New(entry.Date),
Comment: entry.Comment,
})
}
result.Vehicle, err = VehicleFromStorageType(&booking.Vehicle)

View File

@@ -119,8 +119,6 @@ type Booking struct {
Data *structpb.Struct `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"`
Vehicle *Vehicle `protobuf:"bytes,9,opt,name=vehicle,proto3" json:"vehicle,omitempty"`
Deleted bool `protobuf:"varint,10,opt,name=deleted,proto3" json:"deleted,omitempty"`
ManualStatus string `protobuf:"bytes,11,opt,name=manual_status,json=manualStatus,proto3" json:"manual_status,omitempty"`
StatusHistory []*StatusHistoryEntry `protobuf:"bytes,12,rep,name=status_history,json=statusHistory,proto3" json:"status_history,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -225,120 +223,6 @@ func (x *Booking) GetDeleted() bool {
return false
}
func (x *Booking) GetManualStatus() string {
if x != nil {
return x.ManualStatus
}
return ""
}
func (x *Booking) GetStatusHistory() []*StatusHistoryEntry {
if x != nil {
return x.StatusHistory
}
return nil
}
type StatusHistoryEntry struct {
state protoimpl.MessageState `protogen:"open.v1"`
FromStatus string `protobuf:"bytes,1,opt,name=from_status,json=fromStatus,proto3" json:"from_status,omitempty"`
ToStatus string `protobuf:"bytes,2,opt,name=to_status,json=toStatus,proto3" json:"to_status,omitempty"`
UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
UserName string `protobuf:"bytes,4,opt,name=user_name,json=userName,proto3" json:"user_name,omitempty"`
GroupId string `protobuf:"bytes,5,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
GroupName string `protobuf:"bytes,6,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"`
Date *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=date,proto3" json:"date,omitempty"`
Comment string `protobuf:"bytes,8,opt,name=comment,proto3" json:"comment,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *StatusHistoryEntry) Reset() {
*x = StatusHistoryEntry{}
mi := &file_vehicles_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *StatusHistoryEntry) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StatusHistoryEntry) ProtoMessage() {}
func (x *StatusHistoryEntry) ProtoReflect() protoreflect.Message {
mi := &file_vehicles_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StatusHistoryEntry.ProtoReflect.Descriptor instead.
func (*StatusHistoryEntry) Descriptor() ([]byte, []int) {
return file_vehicles_proto_rawDescGZIP(), []int{2}
}
func (x *StatusHistoryEntry) GetFromStatus() string {
if x != nil {
return x.FromStatus
}
return ""
}
func (x *StatusHistoryEntry) GetToStatus() string {
if x != nil {
return x.ToStatus
}
return ""
}
func (x *StatusHistoryEntry) GetUserId() string {
if x != nil {
return x.UserId
}
return ""
}
func (x *StatusHistoryEntry) GetUserName() string {
if x != nil {
return x.UserName
}
return ""
}
func (x *StatusHistoryEntry) GetGroupId() string {
if x != nil {
return x.GroupId
}
return ""
}
func (x *StatusHistoryEntry) GetGroupName() string {
if x != nil {
return x.GroupName
}
return ""
}
func (x *StatusHistoryEntry) GetDate() *timestamppb.Timestamp {
if x != nil {
return x.Date
}
return nil
}
func (x *StatusHistoryEntry) GetComment() string {
if x != nil {
return x.Comment
}
return ""
}
var File_vehicles_proto protoreflect.FileDescriptor
const file_vehicles_proto_rawDesc = "" +
@@ -350,7 +234,7 @@ const file_vehicles_proto_rawDesc = "" +
"\x04type\x18\x03 \x01(\tR\x04type\x12&\n" +
"\x0eadministrators\x18\x04 \x03(\tR\x0eadministrators\x12+\n" +
"\x04data\x18\x05 \x01(\v2\x17.google.protobuf.StructR\x04data\x12$\n" +
"\bbookings\x18\x06 \x03(\v2\b.BookingR\bbookings\"\x93\x04\n" +
"\bbookings\x18\x06 \x03(\v2\b.BookingR\bbookings\"\xb2\x03\n" +
"\aBooking\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" +
"\tvehicleid\x18\x02 \x01(\tR\tvehicleid\x12\x16\n" +
@@ -362,20 +246,7 @@ const file_vehicles_proto_rawDesc = "" +
"\x04data\x18\b \x01(\v2\x17.google.protobuf.StructR\x04data\x12\"\n" +
"\avehicle\x18\t \x01(\v2\b.VehicleR\avehicle\x12\x18\n" +
"\adeleted\x18\n" +
" \x01(\bR\adeleted\x12#\n" +
"\rmanual_status\x18\v \x01(\tR\fmanualStatus\x12:\n" +
"\x0estatus_history\x18\f \x03(\v2\x13.StatusHistoryEntryR\rstatusHistory\"\x8c\x02\n" +
"\x12StatusHistoryEntry\x12\x1f\n" +
"\vfrom_status\x18\x01 \x01(\tR\n" +
"fromStatus\x12\x1b\n" +
"\tto_status\x18\x02 \x01(\tR\btoStatus\x12\x17\n" +
"\auser_id\x18\x03 \x01(\tR\x06userId\x12\x1b\n" +
"\tuser_name\x18\x04 \x01(\tR\buserName\x12\x19\n" +
"\bgroup_id\x18\x05 \x01(\tR\agroupId\x12\x1d\n" +
"\n" +
"group_name\x18\x06 \x01(\tR\tgroupName\x12.\n" +
"\x04date\x18\a \x01(\v2\x1a.google.protobuf.TimestampR\x04date\x12\x18\n" +
"\acomment\x18\b \x01(\tR\acommentB.Z,git.coopgo.io/coopgo-platform/fleets/grpcapib\x06proto3"
" \x01(\bR\adeletedB.Z,git.coopgo.io/coopgo-platform/fleets/grpcapib\x06proto3"
var (
file_vehicles_proto_rawDescOnce sync.Once
@@ -389,30 +260,27 @@ func file_vehicles_proto_rawDescGZIP() []byte {
return file_vehicles_proto_rawDescData
}
var file_vehicles_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_vehicles_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_vehicles_proto_goTypes = []any{
(*Vehicle)(nil), // 0: Vehicle
(*Booking)(nil), // 1: Booking
(*StatusHistoryEntry)(nil), // 2: StatusHistoryEntry
(*structpb.Struct)(nil), // 3: google.protobuf.Struct
(*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
(*structpb.Struct)(nil), // 2: google.protobuf.Struct
(*timestamppb.Timestamp)(nil), // 3: google.protobuf.Timestamp
}
var file_vehicles_proto_depIdxs = []int32{
3, // 0: Vehicle.data:type_name -> google.protobuf.Struct
2, // 0: Vehicle.data:type_name -> google.protobuf.Struct
1, // 1: Vehicle.bookings:type_name -> Booking
4, // 2: Booking.startdate:type_name -> google.protobuf.Timestamp
4, // 3: Booking.enddate:type_name -> google.protobuf.Timestamp
4, // 4: Booking.unavailablefrom:type_name -> google.protobuf.Timestamp
4, // 5: Booking.unavailableto:type_name -> google.protobuf.Timestamp
3, // 6: Booking.data:type_name -> google.protobuf.Struct
3, // 2: Booking.startdate:type_name -> google.protobuf.Timestamp
3, // 3: Booking.enddate:type_name -> google.protobuf.Timestamp
3, // 4: Booking.unavailablefrom:type_name -> google.protobuf.Timestamp
3, // 5: Booking.unavailableto:type_name -> google.protobuf.Timestamp
2, // 6: Booking.data:type_name -> google.protobuf.Struct
0, // 7: Booking.vehicle:type_name -> Vehicle
2, // 8: Booking.status_history:type_name -> StatusHistoryEntry
4, // 9: StatusHistoryEntry.date:type_name -> google.protobuf.Timestamp
10, // [10:10] is the sub-list for method output_type
10, // [10:10] is the sub-list for method input_type
10, // [10:10] is the sub-list for extension type_name
10, // [10:10] is the sub-list for extension extendee
0, // [0:10] is the sub-list for field type_name
8, // [8:8] is the sub-list for method output_type
8, // [8:8] is the sub-list for method input_type
8, // [8:8] is the sub-list for extension type_name
8, // [8:8] is the sub-list for extension extendee
0, // [0:8] is the sub-list for field type_name
}
func init() { file_vehicles_proto_init() }
@@ -426,7 +294,7 @@ func file_vehicles_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_vehicles_proto_rawDesc), len(file_vehicles_proto_rawDesc)),
NumEnums: 0,
NumMessages: 3,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -26,18 +26,4 @@ message Booking {
Vehicle vehicle = 9;
bool deleted = 10;
string manual_status = 11;
repeated StatusHistoryEntry status_history = 12;
}
message StatusHistoryEntry {
string from_status = 1;
string to_status = 2;
string user_id = 3;
string user_name = 4;
string group_id = 5;
string group_name = 6;
google.protobuf.Timestamp date = 7;
string comment = 8;
}

View File

@@ -8,17 +8,6 @@ const (
StatusForthcoming = 1
)
type StatusHistoryEntry struct {
FromStatus string `json:"from_status" bson:"from_status"`
ToStatus string `json:"to_status" bson:"to_status"`
UserID string `json:"user_id" bson:"user_id"`
UserName string `json:"user_name" bson:"user_name"`
GroupID string `json:"group_id" bson:"group_id"`
GroupName string `json:"group_name" bson:"group_name"`
Date time.Time `json:"date" bson:"date"`
Comment string `json:"comment" bson:"comment"`
}
type Booking struct {
ID string `json:"id" bson:"_id"`
Vehicleid string `json:"vehicleid"`
@@ -31,9 +20,6 @@ type Booking struct {
Deleted bool `json:"deleted"`
Vehicle Vehicle `json:"vehicle" bson:"-"`
ManualStatus string `json:"manual_status" bson:"manual_status"`
StatusHistory []StatusHistoryEntry `json:"status_history" bson:"status_history"`
}
func (b Booking) Status() int {