From 87df56f41e4d28bbbf88b60e0c105610e1c008ff Mon Sep 17 00:00:00 2001 From: anxi0uz Date: Tue, 24 Mar 2026 20:25:18 +0500 Subject: [PATCH] to much to describe, everything in PR --- configs/config.toml | 6 + internal/api/api.swagger.yaml | 281 +++++++- internal/api/gen.go | 643 ++++++++++++------ internal/config/config.go | 19 + internal/handler/driver.go | 194 +++++- internal/handler/notification.go | 14 + internal/handler/order.go | 8 +- internal/handler/server_impl.go | 17 +- internal/handler/user.go | 60 +- internal/handler/vehicle.go | 154 ++++- internal/models/notification.go | 16 + .../20260303130559_create_notifications.sql | 14 +- pkg/storage.go | 2 - 13 files changed, 1138 insertions(+), 290 deletions(-) create mode 100644 internal/handler/notification.go create mode 100644 internal/models/notification.go diff --git a/configs/config.toml b/configs/config.toml index 6414f77..659ecb3 100644 --- a/configs/config.toml +++ b/configs/config.toml @@ -22,3 +22,9 @@ accessTokenTTL = "24h" [jwt] issuer = "logiflow-server" audience = "logiflow-client" + +[pricing] +baseFee = 500.0 +perKm = 25.0 +perKg = 3.0 +perM3 = 150.0 diff --git a/internal/api/api.swagger.yaml b/internal/api/api.swagger.yaml index 4def1fa..c05ed64 100644 --- a/internal/api/api.swagger.yaml +++ b/internal/api/api.swagger.yaml @@ -10,9 +10,6 @@ servers: - url: http://localhost:3001 description: Local development -security: - - BearerAuth: [] - components: securitySchemes: BearerAuth: @@ -53,7 +50,7 @@ components: RegisterRequest: type: object - required: [email, password, fullName, role] + required: [email, password, fullName] properties: email: type: string @@ -63,9 +60,6 @@ components: minLength: 8 fullName: type: string - role: - type: string - enum: [admin, manager, driver, client] LoginRequest: type: object @@ -323,11 +317,16 @@ components: ManagerCreate: type: object - required: [userId] + required: [email, password, fullName] properties: - userId: + email: + type: string + format: email + password: + type: string + minLength: 8 + fullName: type: string - format: uuid warehouseId: type: string format: uuid @@ -363,11 +362,16 @@ components: DriverCreate: type: object - required: [userId, licenseNumber, licenseExpiry] + required: [email, password, fullName, licenseNumber, licenseExpiry] properties: - userId: + email: + type: string + format: email + password: + type: string + minLength: 8 + fullName: type: string - format: uuid vehicleId: type: string format: uuid @@ -488,6 +492,84 @@ components: format: uuid nullable: true + # ─── Notification ───────────────────────────────────────────────────────── + + Notification: + type: object + properties: + id: + type: string + format: uuid + userId: + type: string + format: uuid + title: + type: string + body: + type: string + nullable: true + isRead: + type: boolean + createdAt: + type: string + format: date-time + + # ─── Report ─────────────────────────────────────────────────────────────── + + DriverStatusUpdate: + type: object + required: [status] + properties: + status: + type: string + enum: [available, on_route, off_duty] + + # ─── Dashboard ──────────────────────────────────────────────────────────── + + DashboardDriverStat: + type: object + properties: + id: + type: string + format: uuid + fullName: + type: string + status: + type: string + enum: [available, on_route, off_duty] + rating: + type: number + completedOrders: + type: integer + + DashboardReport: + type: object + properties: + revenue: + type: object + properties: + total: + type: number + thisMonth: + type: number + orders: + type: object + properties: + total: + type: integer + delivered: + type: integer + inTransit: + type: integer + pending: + type: integer + cancelled: + type: integer + drivers: + type: array + items: + $ref: "#/components/schemas/DashboardDriverStat" + # ─── Route ──────────────────────────────────────────────────────────────── Coordinate: @@ -555,7 +637,8 @@ paths: /auth/register: post: operationId: authRegister - summary: Регистрация нового пользователя + summary: Регистрация клиента + description: Публичный эндпоинт — создаёт аккаунт с ролью client. Для создания менеджеров и водителей используйте POST /managers и POST /drivers (только admin). tags: [Auth] security: [] requestBody: @@ -706,6 +789,22 @@ paths: "401": description: Не авторизован + /me/trips: + get: + operationId: getMyTrips + summary: Поездки текущего водителя + tags: [Me] + description: Доступно только для роли driver. Возвращает маршруты, назначенные на текущего водителя. + responses: + "200": + description: Список маршрутов + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + "403": + description: Доступно только водителям + # ─── Vehicles ─────────────────────────────────────────────────────────────── /vehicles: @@ -899,7 +998,8 @@ paths: post: operationId: createManager - summary: Назначить менеджера + summary: Создать менеджера + description: Только для роли admin. Создаёт пользователя с ролью manager и профиль менеджера в одной транзакции. tags: [Managers] requestBody: required: true @@ -954,6 +1054,13 @@ paths: operationId: listDrivers summary: Список водителей tags: [Drivers] + parameters: + - name: status + in: query + required: false + schema: + type: string + enum: [available, on_route, off_duty] responses: "200": description: Список водителей @@ -964,7 +1071,8 @@ paths: post: operationId: createDriver - summary: Зарегистрировать водителя + summary: Создать водителя + description: Только для роли admin. Создаёт пользователя с ролью driver и профиль водителя в одной транзакции. tags: [Drivers] requestBody: required: true @@ -1030,6 +1138,27 @@ paths: "200": description: Водитель удалён + /drivers/me/status: + patch: + operationId: updateMyDriverStatus + summary: Водитель меняет свой статус + tags: [Drivers] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/DriverStatusUpdate" + responses: + "200": + description: Статус обновлён + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + "403": + description: Доступно только водителям + # ─── Orders ───────────────────────────────────────────────────────────────── /orders: @@ -1040,6 +1169,19 @@ paths: description: > Клиент видит только свои заявки, менеджер — заявки своего склада, водитель — назначенные на него, администратор — все. + parameters: + - name: status + in: query + required: false + schema: + type: string + enum: [pending, assigned, in_transit, delivered, cancelled] + - name: driverId + in: query + required: false + schema: + type: string + format: uuid responses: "200": description: Список заявок @@ -1191,3 +1333,110 @@ paths: description: Switching Protocols — WebSocket установлен "404": description: Маршрут не найден + + # ─── Notifications ─────────────────────────────────────────────────────────── + + /notifications: + get: + operationId: listNotifications + summary: Список уведомлений текущего пользователя + tags: [Notifications] + parameters: + - name: unreadOnly + in: query + required: false + schema: + type: boolean + responses: + "200": + description: Список уведомлений + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + "401": + description: Не авторизован + + /notifications/{id}/read: + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + + patch: + operationId: markNotificationRead + summary: Отметить уведомление как прочитанное + tags: [Notifications] + responses: + "200": + description: Отмечено + "404": + description: Не найдено + + # ─── Reports ───────────────────────────────────────────────────────────────── + + /reports/orders: + get: + operationId: getOrdersReport + summary: Отчёт по заявкам + tags: [Reports] + description: Доступно для ролей admin и manager. Возвращает агрегированные данные по завершённым заявкам за период. + parameters: + - name: from + in: query + required: false + schema: + type: string + format: date + - name: to + in: query + required: false + schema: + type: string + format: date + - name: status + in: query + required: false + schema: + type: string + enum: [pending, assigned, in_transit, delivered, cancelled] + - name: driverId + in: query + required: false + schema: + type: string + format: uuid + - name: warehouseId + in: query + required: false + schema: + type: string + format: uuid + responses: + "200": + description: Отчёт сформирован + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + "403": + description: Нет доступа + + /reports/dashboard: + get: + operationId: getDashboard + summary: Дашборд менеджмента + description: Доступно для ролей admin и manager. Возвращает агрегированную статистику по выручке, заявкам и водителям. + tags: [Reports] + responses: + "200": + description: Данные дашборда + content: + application/json: + schema: + $ref: "#/components/schemas/ApiResponse" + "403": + description: Нет доступа diff --git a/internal/api/gen.go b/internal/api/gen.go index 557c311..c81fb9a 100644 --- a/internal/api/gen.go +++ b/internal/api/gen.go @@ -4,7 +4,6 @@ package api import ( - "context" "fmt" "net/http" @@ -13,10 +12,27 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) +// Defines values for DriverStatusUpdateStatus. const ( - BearerAuthScopes = "BearerAuth.Scopes" + DriverStatusUpdateStatusAvailable DriverStatusUpdateStatus = "available" + DriverStatusUpdateStatusOffDuty DriverStatusUpdateStatus = "off_duty" + DriverStatusUpdateStatusOnRoute DriverStatusUpdateStatus = "on_route" ) +// Valid indicates whether the value is a known member of the DriverStatusUpdateStatus enum. +func (e DriverStatusUpdateStatus) Valid() bool { + switch e { + case DriverStatusUpdateStatusAvailable: + return true + case DriverStatusUpdateStatusOffDuty: + return true + case DriverStatusUpdateStatusOnRoute: + return true + default: + return false + } +} + // Defines values for DriverUpdateStatus. const ( DriverUpdateStatusAvailable DriverUpdateStatus = "available" @@ -40,46 +56,22 @@ func (e DriverUpdateStatus) Valid() bool { // Defines values for OrderStatusUpdateStatus. const ( - Assigned OrderStatusUpdateStatus = "assigned" - Cancelled OrderStatusUpdateStatus = "cancelled" - Delivered OrderStatusUpdateStatus = "delivered" - InTransit OrderStatusUpdateStatus = "in_transit" + OrderStatusUpdateStatusAssigned OrderStatusUpdateStatus = "assigned" + OrderStatusUpdateStatusCancelled OrderStatusUpdateStatus = "cancelled" + OrderStatusUpdateStatusDelivered OrderStatusUpdateStatus = "delivered" + OrderStatusUpdateStatusInTransit OrderStatusUpdateStatus = "in_transit" ) // Valid indicates whether the value is a known member of the OrderStatusUpdateStatus enum. func (e OrderStatusUpdateStatus) Valid() bool { switch e { - case Assigned: + case OrderStatusUpdateStatusAssigned: return true - case Cancelled: + case OrderStatusUpdateStatusCancelled: return true - case Delivered: + case OrderStatusUpdateStatusDelivered: return true - case InTransit: - return true - default: - return false - } -} - -// Defines values for RegisterRequestRole. -const ( - Admin RegisterRequestRole = "admin" - Client RegisterRequestRole = "client" - Driver RegisterRequestRole = "driver" - Manager RegisterRequestRole = "manager" -) - -// Valid indicates whether the value is a known member of the RegisterRequestRole enum. -func (e RegisterRequestRole) Valid() bool { - switch e { - case Admin: - return true - case Client: - return true - case Driver: - return true - case Manager: + case OrderStatusUpdateStatusInTransit: return true default: return false @@ -125,6 +117,81 @@ func (e WarehouseUpdateStatus) Valid() bool { } } +// Defines values for ListDriversParamsStatus. +const ( + Available ListDriversParamsStatus = "available" + OffDuty ListDriversParamsStatus = "off_duty" + OnRoute ListDriversParamsStatus = "on_route" +) + +// Valid indicates whether the value is a known member of the ListDriversParamsStatus enum. +func (e ListDriversParamsStatus) Valid() bool { + switch e { + case Available: + return true + case OffDuty: + return true + case OnRoute: + return true + default: + return false + } +} + +// Defines values for ListOrdersParamsStatus. +const ( + ListOrdersParamsStatusAssigned ListOrdersParamsStatus = "assigned" + ListOrdersParamsStatusCancelled ListOrdersParamsStatus = "cancelled" + ListOrdersParamsStatusDelivered ListOrdersParamsStatus = "delivered" + ListOrdersParamsStatusInTransit ListOrdersParamsStatus = "in_transit" + ListOrdersParamsStatusPending ListOrdersParamsStatus = "pending" +) + +// Valid indicates whether the value is a known member of the ListOrdersParamsStatus enum. +func (e ListOrdersParamsStatus) Valid() bool { + switch e { + case ListOrdersParamsStatusAssigned: + return true + case ListOrdersParamsStatusCancelled: + return true + case ListOrdersParamsStatusDelivered: + return true + case ListOrdersParamsStatusInTransit: + return true + case ListOrdersParamsStatusPending: + return true + default: + return false + } +} + +// Defines values for GetOrdersReportParamsStatus. +const ( + GetOrdersReportParamsStatusAssigned GetOrdersReportParamsStatus = "assigned" + GetOrdersReportParamsStatusCancelled GetOrdersReportParamsStatus = "cancelled" + GetOrdersReportParamsStatusDelivered GetOrdersReportParamsStatus = "delivered" + GetOrdersReportParamsStatusInTransit GetOrdersReportParamsStatus = "in_transit" + GetOrdersReportParamsStatusPending GetOrdersReportParamsStatus = "pending" +) + +// Valid indicates whether the value is a known member of the GetOrdersReportParamsStatus enum. +func (e GetOrdersReportParamsStatus) Valid() bool { + switch e { + case GetOrdersReportParamsStatusAssigned: + return true + case GetOrdersReportParamsStatusCancelled: + return true + case GetOrdersReportParamsStatusDelivered: + return true + case GetOrdersReportParamsStatusInTransit: + return true + case GetOrdersReportParamsStatusPending: + return true + default: + return false + } +} + // ApiResponse defines model for ApiResponse. type ApiResponse struct { Data *map[string]interface{} `json:"data,omitempty"` @@ -135,12 +202,22 @@ type ApiResponse struct { // DriverCreate defines model for DriverCreate. type DriverCreate struct { + Email openapi_types.Email `json:"email"` + FullName string `json:"fullName"` LicenseExpiry openapi_types.Date `json:"licenseExpiry"` LicenseNumber string `json:"licenseNumber"` - UserId openapi_types.UUID `json:"userId"` + Password string `json:"password"` VehicleId *openapi_types.UUID `json:"vehicleId,omitempty"` } +// DriverStatusUpdate defines model for DriverStatusUpdate. +type DriverStatusUpdate struct { + Status DriverStatusUpdateStatus `json:"status"` +} + +// DriverStatusUpdateStatus defines model for DriverStatusUpdate.Status. +type DriverStatusUpdateStatus string + // DriverUpdate defines model for DriverUpdate. type DriverUpdate struct { LicenseExpiry *openapi_types.Date `json:"licenseExpiry,omitempty"` @@ -167,7 +244,9 @@ type LoginRequest struct { // ManagerCreate defines model for ManagerCreate. type ManagerCreate struct { - UserId openapi_types.UUID `json:"userId"` + Email openapi_types.Email `json:"email"` + FullName string `json:"fullName"` + Password string `json:"password"` WarehouseId *openapi_types.UUID `json:"warehouseId,omitempty"` } @@ -195,12 +274,8 @@ type RegisterRequest struct { Email openapi_types.Email `json:"email"` FullName string `json:"fullName"` Password string `json:"password"` - Role RegisterRequestRole `json:"role"` } -// RegisterRequestRole defines model for RegisterRequest.Role. -type RegisterRequestRole string - // UserDeleteRequest defines model for UserDeleteRequest. type UserDeleteRequest struct { Password string `json:"password"` @@ -259,6 +334,40 @@ type WarehouseUpdate struct { // WarehouseUpdateStatus defines model for WarehouseUpdate.Status. type WarehouseUpdateStatus string +// ListDriversParams defines parameters for ListDrivers. +type ListDriversParams struct { + Status *ListDriversParamsStatus `form:"status,omitempty" json:"status,omitempty"` +} + +// ListDriversParamsStatus defines parameters for ListDrivers. +type ListDriversParamsStatus string + +// ListNotificationsParams defines parameters for ListNotifications. +type ListNotificationsParams struct { + UnreadOnly *bool `form:"unreadOnly,omitempty" json:"unreadOnly,omitempty"` +} + +// ListOrdersParams defines parameters for ListOrders. +type ListOrdersParams struct { + Status *ListOrdersParamsStatus `form:"status,omitempty" json:"status,omitempty"` + DriverId *openapi_types.UUID `form:"driverId,omitempty" json:"driverId,omitempty"` +} + +// ListOrdersParamsStatus defines parameters for ListOrders. +type ListOrdersParamsStatus string + +// GetOrdersReportParams defines parameters for GetOrdersReport. +type GetOrdersReportParams struct { + From *openapi_types.Date `form:"from,omitempty" json:"from,omitempty"` + To *openapi_types.Date `form:"to,omitempty" json:"to,omitempty"` + Status *GetOrdersReportParamsStatus `form:"status,omitempty" json:"status,omitempty"` + DriverId *openapi_types.UUID `form:"driverId,omitempty" json:"driverId,omitempty"` + WarehouseId *openapi_types.UUID `form:"warehouseId,omitempty" json:"warehouseId,omitempty"` +} + +// GetOrdersReportParamsStatus defines parameters for GetOrdersReport. +type GetOrdersReportParamsStatus string + // AuthLoginJSONRequestBody defines body for AuthLogin for application/json ContentType. type AuthLoginJSONRequestBody = LoginRequest @@ -268,6 +377,9 @@ type AuthRegisterJSONRequestBody = RegisterRequest // CreateDriverJSONRequestBody defines body for CreateDriver for application/json ContentType. type CreateDriverJSONRequestBody = DriverCreate +// UpdateMyDriverStatusJSONRequestBody defines body for UpdateMyDriverStatus for application/json ContentType. +type UpdateMyDriverStatusJSONRequestBody = DriverStatusUpdate + // UpdateDriverJSONRequestBody defines body for UpdateDriver for application/json ContentType. type UpdateDriverJSONRequestBody = DriverUpdate @@ -309,15 +421,18 @@ type ServerInterface interface { // Обновление access-токена // (POST /auth/refresh) AuthRefresh(w http.ResponseWriter, r *http.Request) - // Регистрация нового пользователя + // Регистрация клиента // (POST /auth/register) AuthRegister(w http.ResponseWriter, r *http.Request) // Список водителей // (GET /drivers) - ListDrivers(w http.ResponseWriter, r *http.Request) - // Зарегистрировать водителя + ListDrivers(w http.ResponseWriter, r *http.Request, params ListDriversParams) + // Создать водителя // (POST /drivers) CreateDriver(w http.ResponseWriter, r *http.Request) + // Водитель меняет свой статус + // (PATCH /drivers/me/status) + UpdateMyDriverStatus(w http.ResponseWriter, r *http.Request) // Удалить водителя // (DELETE /drivers/{slug}) DeleteDriver(w http.ResponseWriter, r *http.Request, slug string) @@ -330,7 +445,7 @@ type ServerInterface interface { // Список менеджеров // (GET /managers) ListManagers(w http.ResponseWriter, r *http.Request) - // Назначить менеджера + // Создать менеджера // (POST /managers) CreateManager(w http.ResponseWriter, r *http.Request) // Удалить менеджера @@ -348,9 +463,18 @@ type ServerInterface interface { // Обновить профиль // (PATCH /me) UpdateMe(w http.ResponseWriter, r *http.Request) + // Поездки текущего водителя + // (GET /me/trips) + GetMyTrips(w http.ResponseWriter, r *http.Request) + // Список уведомлений текущего пользователя + // (GET /notifications) + ListNotifications(w http.ResponseWriter, r *http.Request, params ListNotificationsParams) + // Отметить уведомление как прочитанное + // (PATCH /notifications/{id}/read) + MarkNotificationRead(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) // Список заявок // (GET /orders) - ListOrders(w http.ResponseWriter, r *http.Request) + ListOrders(w http.ResponseWriter, r *http.Request, params ListOrdersParams) // Создать заявку // (POST /orders) CreateOrder(w http.ResponseWriter, r *http.Request) @@ -369,6 +493,12 @@ type ServerInterface interface { // Обновить статус заявки // (PATCH /orders/{id}/status) UpdateOrderStatus(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) + // Дашборд менеджмента + // (GET /reports/dashboard) + GetDashboard(w http.ResponseWriter, r *http.Request) + // Отчёт по заявкам + // (GET /reports/orders) + GetOrdersReport(w http.ResponseWriter, r *http.Request, params GetOrdersReportParams) // Список транспортных средств // (GET /vehicles) ListVehicles(w http.ResponseWriter, r *http.Request) @@ -423,7 +553,7 @@ func (_ Unimplemented) AuthRefresh(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Регистрация нового пользователя +// Регистрация клиента // (POST /auth/register) func (_ Unimplemented) AuthRegister(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -431,16 +561,22 @@ func (_ Unimplemented) AuthRegister(w http.ResponseWriter, r *http.Request) { // Список водителей // (GET /drivers) -func (_ Unimplemented) ListDrivers(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) ListDrivers(w http.ResponseWriter, r *http.Request, params ListDriversParams) { w.WriteHeader(http.StatusNotImplemented) } -// Зарегистрировать водителя +// Создать водителя // (POST /drivers) func (_ Unimplemented) CreateDriver(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// Водитель меняет свой статус +// (PATCH /drivers/me/status) +func (_ Unimplemented) UpdateMyDriverStatus(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // Удалить водителя // (DELETE /drivers/{slug}) func (_ Unimplemented) DeleteDriver(w http.ResponseWriter, r *http.Request, slug string) { @@ -465,7 +601,7 @@ func (_ Unimplemented) ListManagers(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// Назначить менеджера +// Создать менеджера // (POST /managers) func (_ Unimplemented) CreateManager(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -501,9 +637,27 @@ func (_ Unimplemented) UpdateMe(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// Поездки текущего водителя +// (GET /me/trips) +func (_ Unimplemented) GetMyTrips(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Список уведомлений текущего пользователя +// (GET /notifications) +func (_ Unimplemented) ListNotifications(w http.ResponseWriter, r *http.Request, params ListNotificationsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Отметить уведомление как прочитанное +// (PATCH /notifications/{id}/read) +func (_ Unimplemented) MarkNotificationRead(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + // Список заявок // (GET /orders) -func (_ Unimplemented) ListOrders(w http.ResponseWriter, r *http.Request) { +func (_ Unimplemented) ListOrders(w http.ResponseWriter, r *http.Request, params ListOrdersParams) { w.WriteHeader(http.StatusNotImplemented) } @@ -543,6 +697,18 @@ func (_ Unimplemented) UpdateOrderStatus(w http.ResponseWriter, r *http.Request, w.WriteHeader(http.StatusNotImplemented) } +// Дашборд менеджмента +// (GET /reports/dashboard) +func (_ Unimplemented) GetDashboard(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Отчёт по заявкам +// (GET /reports/orders) +func (_ Unimplemented) GetOrdersReport(w http.ResponseWriter, r *http.Request, params GetOrdersReportParams) { + w.WriteHeader(http.StatusNotImplemented) +} + // Список транспортных средств // (GET /vehicles) func (_ Unimplemented) ListVehicles(w http.ResponseWriter, r *http.Request) { @@ -629,12 +795,6 @@ func (siw *ServerInterfaceWrapper) AuthLogin(w http.ResponseWriter, r *http.Requ // AuthLogout operation middleware func (siw *ServerInterfaceWrapper) AuthLogout(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.AuthLogout(w, r) })) @@ -677,14 +837,21 @@ func (siw *ServerInterfaceWrapper) AuthRegister(w http.ResponseWriter, r *http.R // ListDrivers operation middleware func (siw *ServerInterfaceWrapper) ListDrivers(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + var err error - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params ListDriversParams - r = r.WithContext(ctx) + // ------------- Optional query parameter "status" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "status", r.URL.Query(), ¶ms.Status, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + return + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListDrivers(w, r) + siw.Handler.ListDrivers(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -697,12 +864,6 @@ func (siw *ServerInterfaceWrapper) ListDrivers(w http.ResponseWriter, r *http.Re // CreateDriver operation middleware func (siw *ServerInterfaceWrapper) CreateDriver(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateDriver(w, r) })) @@ -714,6 +875,20 @@ func (siw *ServerInterfaceWrapper) CreateDriver(w http.ResponseWriter, r *http.R handler.ServeHTTP(w, r) } +// UpdateMyDriverStatus operation middleware +func (siw *ServerInterfaceWrapper) UpdateMyDriverStatus(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateMyDriverStatus(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // DeleteDriver operation middleware func (siw *ServerInterfaceWrapper) DeleteDriver(w http.ResponseWriter, r *http.Request) { @@ -728,12 +903,6 @@ func (siw *ServerInterfaceWrapper) DeleteDriver(w http.ResponseWriter, r *http.R return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteDriver(w, r, slug) })) @@ -759,12 +928,6 @@ func (siw *ServerInterfaceWrapper) GetDriver(w http.ResponseWriter, r *http.Requ return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetDriver(w, r, slug) })) @@ -790,12 +953,6 @@ func (siw *ServerInterfaceWrapper) UpdateDriver(w http.ResponseWriter, r *http.R return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateDriver(w, r, slug) })) @@ -810,12 +967,6 @@ func (siw *ServerInterfaceWrapper) UpdateDriver(w http.ResponseWriter, r *http.R // ListManagers operation middleware func (siw *ServerInterfaceWrapper) ListManagers(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListManagers(w, r) })) @@ -830,12 +981,6 @@ func (siw *ServerInterfaceWrapper) ListManagers(w http.ResponseWriter, r *http.R // CreateManager operation middleware func (siw *ServerInterfaceWrapper) CreateManager(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateManager(w, r) })) @@ -861,12 +1006,6 @@ func (siw *ServerInterfaceWrapper) DeleteManager(w http.ResponseWriter, r *http. return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteManager(w, r, slug) })) @@ -892,12 +1031,6 @@ func (siw *ServerInterfaceWrapper) GetManager(w http.ResponseWriter, r *http.Req return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetManager(w, r, slug) })) @@ -912,12 +1045,6 @@ func (siw *ServerInterfaceWrapper) GetManager(w http.ResponseWriter, r *http.Req // DeleteMe operation middleware func (siw *ServerInterfaceWrapper) DeleteMe(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteMe(w, r) })) @@ -932,12 +1059,6 @@ func (siw *ServerInterfaceWrapper) DeleteMe(w http.ResponseWriter, r *http.Reque // GetMe operation middleware func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetMe(w, r) })) @@ -952,12 +1073,6 @@ func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) // UpdateMe operation middleware func (siw *ServerInterfaceWrapper) UpdateMe(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateMe(w, r) })) @@ -969,17 +1084,98 @@ func (siw *ServerInterfaceWrapper) UpdateMe(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r) } +// GetMyTrips operation middleware +func (siw *ServerInterfaceWrapper) GetMyTrips(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetMyTrips(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListNotifications operation middleware +func (siw *ServerInterfaceWrapper) ListNotifications(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params ListNotificationsParams + + // ------------- Optional query parameter "unreadOnly" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "unreadOnly", r.URL.Query(), ¶ms.UnreadOnly, runtime.BindQueryParameterOptions{Type: "boolean", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "unreadOnly", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListNotifications(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// MarkNotificationRead operation middleware +func (siw *ServerInterfaceWrapper) MarkNotificationRead(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "uuid"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.MarkNotificationRead(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ListOrders operation middleware func (siw *ServerInterfaceWrapper) ListOrders(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() + var err error - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context + var params ListOrdersParams - r = r.WithContext(ctx) + // ------------- Optional query parameter "status" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "status", r.URL.Query(), ¶ms.Status, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + return + } + + // ------------- Optional query parameter "driverId" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "driverId", r.URL.Query(), ¶ms.DriverId, runtime.BindQueryParameterOptions{Type: "string", Format: "uuid"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "driverId", Err: err}) + return + } handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListOrders(w, r) + siw.Handler.ListOrders(w, r, params) })) for _, middleware := range siw.HandlerMiddlewares { @@ -992,12 +1188,6 @@ func (siw *ServerInterfaceWrapper) ListOrders(w http.ResponseWriter, r *http.Req // CreateOrder operation middleware func (siw *ServerInterfaceWrapper) CreateOrder(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateOrder(w, r) })) @@ -1023,12 +1213,6 @@ func (siw *ServerInterfaceWrapper) CancelOrder(w http.ResponseWriter, r *http.Re return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CancelOrder(w, r, id) })) @@ -1054,12 +1238,6 @@ func (siw *ServerInterfaceWrapper) GetOrder(w http.ResponseWriter, r *http.Reque return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetOrder(w, r, id) })) @@ -1085,12 +1263,6 @@ func (siw *ServerInterfaceWrapper) GetRoute(w http.ResponseWriter, r *http.Reque return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetRoute(w, r, id) })) @@ -1116,12 +1288,6 @@ func (siw *ServerInterfaceWrapper) RouteWebSocket(w http.ResponseWriter, r *http return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.RouteWebSocket(w, r, id) })) @@ -1147,12 +1313,6 @@ func (siw *ServerInterfaceWrapper) UpdateOrderStatus(w http.ResponseWriter, r *h return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateOrderStatus(w, r, id) })) @@ -1164,15 +1324,82 @@ func (siw *ServerInterfaceWrapper) UpdateOrderStatus(w http.ResponseWriter, r *h handler.ServeHTTP(w, r) } +// GetDashboard operation middleware +func (siw *ServerInterfaceWrapper) GetDashboard(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetDashboard(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetOrdersReport operation middleware +func (siw *ServerInterfaceWrapper) GetOrdersReport(w http.ResponseWriter, r *http.Request) { + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params GetOrdersReportParams + + // ------------- Optional query parameter "from" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "from", r.URL.Query(), ¶ms.From, runtime.BindQueryParameterOptions{Type: "string", Format: "date"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "from", Err: err}) + return + } + + // ------------- Optional query parameter "to" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "to", r.URL.Query(), ¶ms.To, runtime.BindQueryParameterOptions{Type: "string", Format: "date"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "to", Err: err}) + return + } + + // ------------- Optional query parameter "status" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "status", r.URL.Query(), ¶ms.Status, runtime.BindQueryParameterOptions{Type: "string", Format: ""}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "status", Err: err}) + return + } + + // ------------- Optional query parameter "driverId" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "driverId", r.URL.Query(), ¶ms.DriverId, runtime.BindQueryParameterOptions{Type: "string", Format: "uuid"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "driverId", Err: err}) + return + } + + // ------------- Optional query parameter "warehouseId" ------------- + + err = runtime.BindQueryParameterWithOptions("form", true, false, "warehouseId", r.URL.Query(), ¶ms.WarehouseId, runtime.BindQueryParameterOptions{Type: "string", Format: "uuid"}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "warehouseId", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetOrdersReport(w, r, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ListVehicles operation middleware func (siw *ServerInterfaceWrapper) ListVehicles(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListVehicles(w, r) })) @@ -1187,12 +1414,6 @@ func (siw *ServerInterfaceWrapper) ListVehicles(w http.ResponseWriter, r *http.R // CreateVehicle operation middleware func (siw *ServerInterfaceWrapper) CreateVehicle(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateVehicle(w, r) })) @@ -1218,12 +1439,6 @@ func (siw *ServerInterfaceWrapper) DeleteVehicle(w http.ResponseWriter, r *http. return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteVehicle(w, r, slug) })) @@ -1249,12 +1464,6 @@ func (siw *ServerInterfaceWrapper) GetVehicle(w http.ResponseWriter, r *http.Req return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetVehicle(w, r, slug) })) @@ -1280,12 +1489,6 @@ func (siw *ServerInterfaceWrapper) UpdateVehicle(w http.ResponseWriter, r *http. return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateVehicle(w, r, slug) })) @@ -1300,12 +1503,6 @@ func (siw *ServerInterfaceWrapper) UpdateVehicle(w http.ResponseWriter, r *http. // ListWarehouses operation middleware func (siw *ServerInterfaceWrapper) ListWarehouses(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.ListWarehouses(w, r) })) @@ -1320,12 +1517,6 @@ func (siw *ServerInterfaceWrapper) ListWarehouses(w http.ResponseWriter, r *http // CreateWarehouse operation middleware func (siw *ServerInterfaceWrapper) CreateWarehouse(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.CreateWarehouse(w, r) })) @@ -1351,12 +1542,6 @@ func (siw *ServerInterfaceWrapper) DeleteWarehouse(w http.ResponseWriter, r *htt return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.DeleteWarehouse(w, r, slug) })) @@ -1382,12 +1567,6 @@ func (siw *ServerInterfaceWrapper) GetWarehouse(w http.ResponseWriter, r *http.R return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetWarehouse(w, r, slug) })) @@ -1413,12 +1592,6 @@ func (siw *ServerInterfaceWrapper) UpdateWarehouse(w http.ResponseWriter, r *htt return } - ctx := r.Context() - - ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) - - r = r.WithContext(ctx) - handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.UpdateWarehouse(w, r, slug) })) @@ -1561,6 +1734,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/drivers", wrapper.CreateDriver) }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/drivers/me/status", wrapper.UpdateMyDriverStatus) + }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/drivers/{slug}", wrapper.DeleteDriver) }) @@ -1591,6 +1767,15 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Patch(options.BaseURL+"/me", wrapper.UpdateMe) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/me/trips", wrapper.GetMyTrips) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/notifications", wrapper.ListNotifications) + }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/notifications/{id}/read", wrapper.MarkNotificationRead) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/orders", wrapper.ListOrders) }) @@ -1612,6 +1797,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Patch(options.BaseURL+"/orders/{id}/status", wrapper.UpdateOrderStatus) }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/reports/dashboard", wrapper.GetDashboard) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/reports/orders", wrapper.GetOrdersReport) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/vehicles", wrapper.ListVehicles) }) diff --git a/internal/config/config.go b/internal/config/config.go index ea9bcd0..8fd3332 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -57,6 +57,13 @@ type Config struct { Issuer string `koanf:"issuer"` Audience string `koanf:"audience"` } `koanf:"jwt"` + + Pricing struct { + BaseFee float64 `koanf:"baseFee"` + PerKm float64 `koanf:"perKm"` + PerKg float64 `koanf:"perKg"` + PerM3 float64 `koanf:"perM3"` + } `koanf:"pricing"` } func NewConfig(ctx context.Context, configPath string) (*Config, error) { @@ -152,6 +159,18 @@ func (c *Config) setDefaults() { if c.Redis.Addr == "" { c.Redis.Addr = "localhost:6379" } + if c.Pricing.BaseFee == 0 { + c.Pricing.BaseFee = 500.0 + } + if c.Pricing.PerKm == 0 { + c.Pricing.PerKm = 25.0 + } + if c.Pricing.PerKg == 0 { + c.Pricing.PerKg = 3.0 + } + if c.Pricing.PerM3 == 0 { + c.Pricing.PerM3 = 150.0 + } } func (c *Config) parseDurations() error { diff --git a/internal/handler/driver.go b/internal/handler/driver.go index 96bbb2a..1962ffd 100644 --- a/internal/handler/driver.go +++ b/internal/handler/driver.go @@ -1,13 +1,195 @@ package handler -import "net/http" +import ( + "encoding/json" + "errors" + "log/slog" + "net/http" + "time" -func (s *Server) ListDrivers(w http.ResponseWriter, r *http.Request) {} + "github.com/anxi0uz/logiflow/internal/api" + "github.com/anxi0uz/logiflow/internal/models" + storage "github.com/anxi0uz/logiflow/pkg" + "github.com/google/uuid" + "github.com/gosimple/slug" + "github.com/huandu/go-sqlbuilder" + "golang.org/x/crypto/bcrypt" +) -func (s *Server) CreateDriver(w http.ResponseWriter, r *http.Request) {} +func (s *Server) ListDrivers(w http.ResponseWriter, r *http.Request, params api.ListDriversParams) {} -func (s *Server) GetDriver(w http.ResponseWriter, r *http.Request, slug string) {} +func (s *Server) CreateDriver(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + claims := ctx.Value("user").(*Claims) + if claims.Role != "admin" { + slog.WarnContext(ctx, "unusual try from not allowed role", slog.String("Role", claims.Role), slog.String("id", claims.ID.String())) + s.JSON(w, r, http.StatusForbidden, MsgForbidden, RespError) + return + } + var req api.DriverCreate -func (s *Server) UpdateDriver(w http.ResponseWriter, r *http.Request, slug string) {} + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + slog.ErrorContext(ctx, "Invalid request body", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError) + return + } + passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + slog.ErrorContext(ctx, "error while generating passwordhash", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + now := time.Now() + userid := uuid.New() + user := models.User{ + ID: userid, + Slug: s.GenerateUserSlug(req.FullName, userid), + CreatedAt: now, + UpdatedAt: now, + Role: "driver", + Email: string(req.Email), + PasswordHash: string(passwordHash), + FullName: req.FullName, + } -func (s *Server) DeleteDriver(w http.ResponseWriter, r *http.Request, slug string) {} + tx, err := s.DB.Begin(ctx) + if err != nil { + slog.ErrorContext(ctx, "Unable to open transaction", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + defer tx.Rollback(ctx) + + if err := storage.Create(ctx, "users", user, tx); err != nil { + slog.ErrorContext(ctx, "Unable to create user", slog.String("error", err.Error()), slog.Any("user", user)) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + + id := uuid.New() + driver := models.Driver{ + ID: id, + UserID: userid, + VehicleID: req.VehicleId, + LicenseNumber: req.LicenseNumber, + LicenseExpiry: req.LicenseExpiry.Time, + Rating: 0, + Slug: slug.Make(req.FullName + " " + req.LicenseNumber), + } + + if err := storage.Create(ctx, "drivers", driver, tx); err != nil { + slog.ErrorContext(ctx, "Unable to create driver", slog.String("error", err.Error()), slog.Any("driver", driver)) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + + if err := tx.Commit(ctx); err != nil { + slog.ErrorContext(ctx, "Error while commiting transaction", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusCreated, driver, "driver") +} + +func (s *Server) UpdateMyDriverStatus(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + claims, ok := ctx.Value("user").(*Claims) + if !ok { + slog.ErrorContext(ctx, "Unable to convert claims", slog.Any("claims", ctx.Value("user"))) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + + var req api.DriverStatusUpdate + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError) + return + } + + if err := storage.Update(ctx, "drivers", models.Driver{Status: string(req.Status)}, s.DB, func(ub *sqlbuilder.UpdateBuilder) { + ub.Where(ub.Equal("user_id", claims.ID)) + }); err != nil { + if errors.Is(err, storage.ErrNotFound) { + slog.ErrorContext(ctx, "No driver with that user id not found", slog.String("user id", claims.ID.String())) + s.JSON(w, r, http.StatusNotFound, "driver not found", RespNotFound) + return + } + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + + s.JSON(w, r, http.StatusOK, "status updated", RespSuccess) +} + +func (s *Server) GetDriver(w http.ResponseWriter, r *http.Request, slug string) { + ctx := r.Context() + + driver, err := storage.GetOne[models.Driver](ctx, s.DB, "drivers", func(sb *sqlbuilder.SelectBuilder) { + sb.Where(sb.Equal("slug", slug)) + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + slog.ErrorContext(ctx, "No driver found with that slug", slog.String("slug", slug)) + s.JSON(w, r, http.StatusNotFound, MsgNotFound, RespNotFound) + return + } + slog.ErrorContext(ctx, "Error while finding driver with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + + s.JSON(w, r, http.StatusOK, driver, RespSuccess) +} + +func (s *Server) UpdateDriver(w http.ResponseWriter, r *http.Request, slug string) { + ctx := r.Context() + + var req api.DriverUpdate + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + slog.ErrorContext(ctx, "Invalid request body", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusBadRequest, MsgInternalError, RespError) + return + } + + driver := models.Driver{ + LicenseNumber: *req.LicenseNumber, + LicenseExpiry: req.LicenseExpiry.Time, + Status: string(*req.Status), + VehicleID: req.VehicleId, + } + + if err := storage.Update(ctx, "drivers", driver, s.DB, func(sb *sqlbuilder.UpdateBuilder) { + sb.Where(sb.Equal("slug", slug)) + }); err != nil { + if errors.Is(err, storage.ErrNotFound) { + slog.ErrorContext(ctx, "no drivers found with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusNotFound, MsgNotFound, RespNotFound) + return + } + slog.ErrorContext(ctx, "Error while updating driver with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusOK, slug, RespSuccess) +} + +func (s *Server) DeleteDriver(w http.ResponseWriter, r *http.Request, slug string) { + ctx := r.Context() + + err := storage.Delete[models.Driver](ctx, "drivers", s.DB, func(sb *sqlbuilder.DeleteBuilder) { + sb.Where(sb.Equal("slug", slug)) + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + s.JSON(w, r, http.StatusNotFound, "No driver with that slug found", RespNotFound) + return + } + slog.ErrorContext(ctx, "unable to delete vehicle with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusOK, slug, "deleted") + +} diff --git a/internal/handler/notification.go b/internal/handler/notification.go new file mode 100644 index 0000000..c2b1fd1 --- /dev/null +++ b/internal/handler/notification.go @@ -0,0 +1,14 @@ +package handler + +import ( + "net/http" + + "github.com/anxi0uz/logiflow/internal/api" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +func (s *Server) ListNotifications(w http.ResponseWriter, r *http.Request, params api.ListNotificationsParams) { +} + +func (s *Server) MarkNotificationRead(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { +} diff --git a/internal/handler/order.go b/internal/handler/order.go index 7166494..4c8c8e9 100644 --- a/internal/handler/order.go +++ b/internal/handler/order.go @@ -3,10 +3,11 @@ package handler import ( "net/http" + "github.com/anxi0uz/logiflow/internal/api" openapi_types "github.com/oapi-codegen/runtime/types" ) -func (s *Server) ListOrders(w http.ResponseWriter, r *http.Request) {} +func (s *Server) ListOrders(w http.ResponseWriter, r *http.Request, params api.ListOrdersParams) {} func (s *Server) CreateOrder(w http.ResponseWriter, r *http.Request) {} @@ -15,3 +16,8 @@ func (s *Server) GetOrder(w http.ResponseWriter, r *http.Request, id openapi_typ 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) {} + +func (s *Server) GetOrdersReport(w http.ResponseWriter, r *http.Request, params api.GetOrdersReportParams) { +} + +func (s *Server) GetDashboard(w http.ResponseWriter, r *http.Request) {} diff --git a/internal/handler/server_impl.go b/internal/handler/server_impl.go index f549f50..a4549ea 100644 --- a/internal/handler/server_impl.go +++ b/internal/handler/server_impl.go @@ -26,6 +26,19 @@ type ctxKey string const ( requestIDKey ctxKey = "X-Request-ID" tokenKey ctxKey = "Authorization" + + // response messages + MsgInternalError = "Internal server error" + MsgInvalidBody = "Invalid request body" + MsgNotFound = "Not found" + MsgUnauthorized = "Unauthorized" + MsgMissingToken = "Missing token" + MsgForbidden = "Forbidden" + + // response types + RespError = "error" + RespSuccess = "success" + RespNotFound = "not found" ) type responseOptions struct { @@ -112,7 +125,7 @@ func (s *Server) AuthMiddleware(next http.Handler) http.Handler { tokenStr := r.Header.Get("Authorization") if tokenStr == "" { - s.JSON(w, r, http.StatusUnauthorized, "missing token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgMissingToken, RespError) return } @@ -121,7 +134,7 @@ func (s *Server) AuthMiddleware(next http.Handler) http.Handler { claims, err := s.validateAccessToken(r.Context(), tokenStr) if err != nil { slog.WarnContext(r.Context(), "Токен не прошел валидацию", slog.String("Token", tokenStr), slog.String("error", err.Error())) - s.JSON(w, r, http.StatusUnauthorized, "токен не прошёл валидацию", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } diff --git a/internal/handler/user.go b/internal/handler/user.go index 3f1b1ad..597a65f 100644 --- a/internal/handler/user.go +++ b/internal/handler/user.go @@ -33,7 +33,7 @@ func (s *Server) AuthLogin(w http.ResponseWriter, r *http.Request) { var req api.LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - s.JSON(w, r, http.StatusBadRequest, "Ошибка при получении данных", "error") + s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError) return } @@ -65,32 +65,32 @@ func (s *Server) AuthLogout(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("refresh_token") if err != nil { - s.JSON(w, r, http.StatusUnauthorized, "Missing refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgMissingToken, RespError) return } refreshStr := cookie.Value if refreshStr == "" { - s.JSON(w, r, http.StatusUnauthorized, "Empty refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } refreshKey := "refresh_token:" + refreshStr if err := s.Redis.Del(ctx, refreshKey).Err(); err != nil { slog.ErrorContext(ctx, "Error while removing refresh token from redis", slog.String("token", refreshStr), slog.String("error", err.Error())) - s.JSON(w, r, http.StatusUnauthorized, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } tokenStr := r.Header.Get("Authorization") if tokenStr == "" { - s.JSON(w, r, http.StatusUnauthorized, "missing token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgMissingToken, RespError) return } tokenKey := "access_token:" + tokenStr if err := s.Redis.Del(ctx, tokenKey).Err(); err != nil { slog.ErrorContext(ctx, "Error while removing access token from redis", slog.String("token", tokenStr), slog.String("error", err.Error())) - s.JSON(w, r, http.StatusUnauthorized, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } } @@ -99,17 +99,17 @@ func (s *Server) AuthRefresh(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("refresh_token") if err != nil { - s.JSON(w, r, http.StatusUnauthorized, "Missing refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgMissingToken, RespError) return } refreshStr := cookie.Value if refreshStr == "" { - s.JSON(w, r, http.StatusUnauthorized, "Empty refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } key := "refresh_token:" + refreshStr if _, err := s.Redis.Get(ctx, key).Result(); err == redis.Nil { - s.JSON(w, r, http.StatusUnauthorized, "No active refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } @@ -119,13 +119,13 @@ func (s *Server) AuthRefresh(w http.ResponseWriter, r *http.Request) { if !ok { slog.ErrorContext(ctx, "Error parsing claims", slog.Any("claims", claims)) - s.JSON(w, r, http.StatusInternalServerError, nil, "internal server error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } userID := claims.ID if userID == uuid.Nil { - s.JSON(w, r, http.StatusUnauthorized, "Missing user id in token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } @@ -134,7 +134,7 @@ func (s *Server) AuthRefresh(w http.ResponseWriter, r *http.Request) { }) if err != nil { slog.ErrorContext(ctx, "user with that id not found", slog.Any("id", userID.String()), "error", err.Error()) - s.JSON(w, r, http.StatusUnauthorized, "Invalid user id in token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } s.issueTokens(w, r, user) @@ -146,13 +146,13 @@ func (s *Server) AuthRegister(w http.ResponseWriter, r *http.Request) { var req api.RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - s.JSON(w, r, http.StatusBadRequest, "invalid request body", "error") + s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError) return } passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { - s.JSON(w, r, http.StatusInternalServerError, "error while hashing password", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } @@ -165,7 +165,7 @@ func (s *Server) AuthRegister(w http.ResponseWriter, r *http.Request) { Slug: s.GenerateUserSlug(req.FullName, uuid), CreatedAt: now, UpdatedAt: now, - Role: string(req.Role), + Role: "client", Email: string(req.Email), PasswordHash: string(passwordHash), FullName: req.FullName, @@ -173,7 +173,7 @@ func (s *Server) AuthRegister(w http.ResponseWriter, r *http.Request) { if err := storage.Create(ctx, "users", user, s.DB); err != nil { slog.ErrorContext(ctx, "Error while creating user", slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "Server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } s.issueTokens(w, r, &user) @@ -185,14 +185,14 @@ func (s *Server) DeleteMe(w http.ResponseWriter, r *http.Request) { claims, ok := claimsValue.(*Claims) if !ok { slog.ErrorContext(ctx, "Error while converting claims", slog.Any("claims", claimsValue)) - s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } userID := claims.ID tx, err := s.DB.Begin(ctx) if err != nil { slog.ErrorContext(ctx, "Error while begining transaction", slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } defer tx.Rollback(ctx) @@ -202,7 +202,7 @@ func (s *Server) DeleteMe(w http.ResponseWriter, r *http.Request) { }) if err != nil { slog.ErrorContext(ctx, "Error while deleting user", slog.String("error", err.Error()), slog.String("id", userID.String())) - s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } @@ -210,29 +210,29 @@ func (s *Server) DeleteMe(w http.ResponseWriter, r *http.Request) { tokenKey := "access_token:" + jwt if err := s.Redis.Del(ctx, tokenKey).Err(); err != nil { 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, MsgInternalError, RespError) return } cookie, err := r.Cookie("refresh_token") if err != nil { - s.JSON(w, r, http.StatusUnauthorized, "Missing refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgMissingToken, RespError) return } refreshStr := cookie.Value if refreshStr == "" { - s.JSON(w, r, http.StatusUnauthorized, "Empty refresh token", "error") + s.JSON(w, r, http.StatusUnauthorized, MsgUnauthorized, RespError) return } key := "refresh_token:" + refreshStr if err := s.Redis.Del(ctx, key).Err(); err != nil { slog.ErrorContext(ctx, "Error while deleting refresh token from redis", slog.String("token", refreshStr)) - s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } if err := tx.Commit(ctx); err != nil { slog.ErrorContext(ctx, "Error while committing transaction", slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } @@ -246,7 +246,7 @@ func (s *Server) GetMe(w http.ResponseWriter, r *http.Request) { claims, ok := claimsValue.(*Claims) if !ok { slog.ErrorContext(ctx, "Error while converting claims", slog.Any("claims", claimsValue)) - s.JSON(w, r, http.StatusInternalServerError, "Internal server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } @@ -258,13 +258,15 @@ func (s *Server) GetMe(w http.ResponseWriter, r *http.Request) { slog.ErrorContext(ctx, "No user was found with that id", slog.String("id", userID.String()), slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "invalid user id", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } s.JSON(w, r, http.StatusOK, user, "ok") } func (s *Server) UpdateMe(w http.ResponseWriter, r *http.Request) {} +func (s *Server) GetMyTrips(w http.ResponseWriter, r *http.Request) {} + func (s *Server) issueTokens(w http.ResponseWriter, r *http.Request, user *models.User) { access, err := s.generateAccessToken(user, s.Config.RedisAccessTokenDur()) if err != nil { @@ -274,7 +276,7 @@ func (s *Server) issueTokens(w http.ResponseWriter, r *http.Request, user *model refresh, err := s.generateRefreshToken() if err != nil { slog.ErrorContext(r.Context(), "generate refresh token failed", slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "Failure during generating tokens", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } @@ -283,14 +285,14 @@ func (s *Server) issueTokens(w http.ResponseWriter, r *http.Request, user *model err = s.Redis.Set(r.Context(), key, "valid", s.Config.RedisAccessTokenDur()).Err() if err != nil { slog.ErrorContext(r.Context(), "Failed to set access token in redis", slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "Server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } err = s.Redis.Set(r.Context(), refreshkey, "valid", s.Config.RedisRefreshTokenDur()).Err() if err != nil { slog.ErrorContext(r.Context(), "Failed to set refresh token in redis", slog.String("error", err.Error())) - s.JSON(w, r, http.StatusInternalServerError, "Server error", "error") + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) return } diff --git a/internal/handler/vehicle.go b/internal/handler/vehicle.go index 59f6260..3220775 100644 --- a/internal/handler/vehicle.go +++ b/internal/handler/vehicle.go @@ -1,13 +1,155 @@ package handler -import "net/http" +import ( + "encoding/json" + "errors" + "log/slog" + "net/http" -func (s *Server) ListVehicles(w http.ResponseWriter, r *http.Request) {} + "github.com/anxi0uz/logiflow/internal/api" + "github.com/anxi0uz/logiflow/internal/models" + storage "github.com/anxi0uz/logiflow/pkg" + "github.com/google/uuid" + "github.com/gosimple/slug" + "github.com/huandu/go-sqlbuilder" +) -func (s *Server) CreateVehicle(w http.ResponseWriter, r *http.Request) {} +func (s *Server) ListVehicles(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() -func (s *Server) GetVehicle(w http.ResponseWriter, r *http.Request, slug string) {} + vehicles, err := storage.GetAll[models.Vehicle](ctx, "vehicles", s.DB) + if err != nil { + slog.ErrorContext(ctx, "Unable to get all vehicles", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusOK, vehicles, RespSuccess) +} -func (s *Server) UpdateVehicle(w http.ResponseWriter, r *http.Request, slug string) {} +func (s *Server) CreateVehicle(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() -func (s *Server) DeleteVehicle(w http.ResponseWriter, r *http.Request, slug string) {} + var req api.VehicleCreate + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + slog.ErrorContext(ctx, "Invalid request body", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError) + return + } + + id := uuid.New() + vehicle := models.Vehicle{ + ID: id, + PlateNumber: req.PlateNumber, + Slug: slug.Make(req.PlateNumber), + } + + if req.Brand != nil { + vehicle.Brand = *req.Brand + } + if req.Model != nil { + vehicle.Model = *req.Model + } + if req.Year != nil { + vehicle.Year = *req.Year + } + if req.CapacityKg != nil { + vehicle.CapacityKg = float64(*req.CapacityKg) + } + if req.CapacityM3 != nil { + vehicle.CapacityM3 = float64(*req.CapacityM3) + } + + if err := storage.Create(ctx, "vehicles", vehicle, s.DB); err != nil { + slog.ErrorContext(ctx, "Unable to create vehicle", slog.Any("vehicle", vehicle)) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusCreated, vehicle, "vehicle") +} + +func (s *Server) GetVehicle(w http.ResponseWriter, r *http.Request, slug string) { + ctx := r.Context() + + vehicle, err := storage.GetOne[models.Vehicle](ctx, s.DB, "vehicles", func(sb *sqlbuilder.SelectBuilder) { + sb.Where(sb.Equal("slug", slug)) + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + s.JSON(w, r, http.StatusNotFound, "vehicle not found with that slug", RespNotFound) + return + } + slog.ErrorContext(ctx, "no vehicle found in db with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusOK, vehicle, "vehicle") +} + +func (s *Server) UpdateVehicle(w http.ResponseWriter, r *http.Request, slug string) { + ctx := r.Context() + + var req api.VehicleUpdate + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + slog.ErrorContext(ctx, "Invalid request body", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusBadRequest, MsgInvalidBody, RespError) + return + } + + vehicle, err := storage.GetOne[models.Vehicle](ctx, s.DB, "vehicles", func(sb *sqlbuilder.SelectBuilder) { + sb.Where(sb.Equal("slug", slug)) + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + s.JSON(w, r, http.StatusNotFound, "vehicle not found with that slug", RespNotFound) + return + } + slog.ErrorContext(ctx, "no vehicle found in db with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + if req.Status != nil { + vehicle.Status = string(*req.Status) + } + if req.Brand != nil { + vehicle.Brand = *req.Brand + } + if req.Model != nil { + vehicle.Model = *req.Model + } + if req.Year != nil { + vehicle.Year = *req.Year + } + if req.CapacityKg != nil { + vehicle.CapacityKg = float64(*req.CapacityKg) + } + if req.CapacityM3 != nil { + vehicle.CapacityM3 = float64(*req.CapacityM3) + } + if err := storage.Update(ctx, "vehicles", *vehicle, s.DB, func(ub *sqlbuilder.UpdateBuilder) { + ub.Where(ub.Equal("slug", slug)) + }); err != nil { + slog.ErrorContext(ctx, "failed to update vehicle", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusOK, vehicle, "vehicle") +} + +func (s *Server) DeleteVehicle(w http.ResponseWriter, r *http.Request, slug string) { + ctx := r.Context() + + err := storage.Delete[models.Vehicle](ctx, "vehicles", s.DB, func(sb *sqlbuilder.DeleteBuilder) { + sb.Where(sb.Equal("slug", slug)) + }) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + s.JSON(w, r, http.StatusNotFound, "no vehicle with that slug found", RespNotFound) + return + } + slog.ErrorContext(ctx, "unable to delete vehicle with that slug", slog.String("slug", slug), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, MsgInternalError, RespError) + return + } + s.JSON(w, r, http.StatusOK, slug, "deleted") +} diff --git a/internal/models/notification.go b/internal/models/notification.go new file mode 100644 index 0000000..795a4f8 --- /dev/null +++ b/internal/models/notification.go @@ -0,0 +1,16 @@ +package models + +import ( + "time" + + "github.com/google/uuid" +) + +type Notification struct { + ID uuid.UUID `db:"id"` + UserID uuid.UUID `db:"user_id"` + Title string `db:"title"` + Body *string `db:"body"` + IsRead bool `db:"is_read"` + CreatedAt time.Time `db:"created_at"` +} diff --git a/migrations/20260303130559_create_notifications.sql b/migrations/20260303130559_create_notifications.sql index b9c449e..427cc39 100644 --- a/migrations/20260303130559_create_notifications.sql +++ b/migrations/20260303130559_create_notifications.sql @@ -1,9 +1,19 @@ -- +goose Up -- +goose StatementBegin -SELECT 'up SQL query'; +CREATE TABLE notifications ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title TEXT NOT NULL, + body TEXT, + is_read BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX idx_notifications_user_id ON notifications(user_id); +CREATE INDEX idx_notifications_user_unread ON notifications(user_id) WHERE is_read = FALSE; -- +goose StatementEnd -- +goose Down -- +goose StatementBegin -SELECT 'down SQL query'; +DROP TABLE IF EXISTS notifications; -- +goose StatementEnd diff --git a/pkg/storage.go b/pkg/storage.go index 7cee172..b8af72a 100644 --- a/pkg/storage.go +++ b/pkg/storage.go @@ -23,8 +23,6 @@ func GetAll[T any](ctx context.Context, table string, db Querier, opts ...func(* sb.From(table) - sb.OrderByDesc("created_at") - for _, opt := range opts { opt(sb) }