Compare commits
3 Commits
fc46fb372f
...
b36a6fb262
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b36a6fb262 | ||
|
|
d3cd92b9f3 | ||
|
|
10233808f4 |
@@ -0,0 +1,34 @@
|
|||||||
|
# Stage 1: Builder
|
||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install git and build dependencies
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Copy go mod files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o api ./cmd/api
|
||||||
|
|
||||||
|
# Stage 2: Production
|
||||||
|
FROM alpine:latest AS production
|
||||||
|
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy binary from builder
|
||||||
|
COPY --from=builder /app/api .
|
||||||
|
|
||||||
|
# Copy migrations
|
||||||
|
COPY internal/db/migrations ./migrations
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["./api"]
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ func main() {
|
|||||||
|
|
||||||
r.GET("/api/deliveries", h.GetDeliveries)
|
r.GET("/api/deliveries", h.GetDeliveries)
|
||||||
r.GET("/api/deliveries/:id", h.GetDeliveryByID)
|
r.GET("/api/deliveries/:id", h.GetDeliveryByID)
|
||||||
|
r.GET("/api/deliveries/count", h.GetDeliveryCount)
|
||||||
r.POST("/api/deliveries", h.CreateDelivery)
|
r.POST("/api/deliveries", h.CreateDelivery)
|
||||||
r.PATCH("/api/deliveries/:id", h.UpdateDelivery)
|
r.PATCH("/api/deliveries/:id", h.UpdateDelivery)
|
||||||
|
r.PATCH("/api/deliveries/:id/status", h.UpdateDeliveryStatus)
|
||||||
r.DELETE("/api/deliveries/:id", h.DeleteDelivery)
|
r.DELETE("/api/deliveries/:id", h.DeleteDelivery)
|
||||||
|
|
||||||
r.Run(":8080")
|
r.Run(":8080")
|
||||||
|
|||||||
@@ -13,4 +13,10 @@ SELECT * FROM deliveries WHERE id = $1;
|
|||||||
DELETE FROM deliveries WHERE id = $1;
|
DELETE FROM deliveries WHERE id = $1;
|
||||||
|
|
||||||
-- name: UpdateDelivery :exec
|
-- name: UpdateDelivery :exec
|
||||||
UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9;
|
UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9;
|
||||||
|
|
||||||
|
-- name: GetDeliveryCount :many
|
||||||
|
SELECT COUNT(*) as count, date FROM deliveries WHERE date >= DATE_TRUNC('month', CURRENT_DATE) GROUP BY date;
|
||||||
|
|
||||||
|
-- name: UpdateDeliveryStatus :exec
|
||||||
|
UPDATE deliveries SET status = $1, updated_at = NOW() WHERE id = $2;
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ type Querier interface {
|
|||||||
DeleteDelivery(ctx context.Context, id pgtype.UUID) error
|
DeleteDelivery(ctx context.Context, id pgtype.UUID) error
|
||||||
GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([]Delivery, error)
|
GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([]Delivery, error)
|
||||||
GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error)
|
GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error)
|
||||||
|
GetDeliveryCount(ctx context.Context) ([]GetDeliveryCountRow, error)
|
||||||
UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) error
|
UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) error
|
||||||
|
UpdateDeliveryStatus(ctx context.Context, arg UpdateDeliveryStatusParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Querier = (*Queries)(nil)
|
var _ Querier = (*Queries)(nil)
|
||||||
|
|||||||
@@ -127,6 +127,35 @@ func (q *Queries) GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDeliveryCount = `-- name: GetDeliveryCount :many
|
||||||
|
SELECT COUNT(*) as count, date FROM deliveries WHERE date >= DATE_TRUNC('month', CURRENT_DATE) GROUP BY date
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetDeliveryCountRow struct {
|
||||||
|
Count int64 `db:"count" json:"count"`
|
||||||
|
Date pgtype.Date `db:"date" json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetDeliveryCount(ctx context.Context) ([]GetDeliveryCountRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getDeliveryCount)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
items := []GetDeliveryCountRow{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetDeliveryCountRow
|
||||||
|
if err := rows.Scan(&i.Count, &i.Date); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const updateDelivery = `-- name: UpdateDelivery :exec
|
const updateDelivery = `-- name: UpdateDelivery :exec
|
||||||
UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9
|
UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9
|
||||||
`
|
`
|
||||||
@@ -157,3 +186,17 @@ func (q *Queries) UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams)
|
|||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateDeliveryStatus = `-- name: UpdateDeliveryStatus :exec
|
||||||
|
UPDATE deliveries SET status = $1, updated_at = NOW() WHERE id = $2
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateDeliveryStatusParams struct {
|
||||||
|
Status string `db:"status" json:"status"`
|
||||||
|
ID pgtype.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateDeliveryStatus(ctx context.Context, arg UpdateDeliveryStatusParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, updateDeliveryStatus, arg.Status, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,6 +69,17 @@ func (h *Handler) GetDeliveries(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"deliveries": deliveries})
|
c.JSON(http.StatusOK, gin.H{"deliveries": deliveries})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/deliveries/count
|
||||||
|
func (h *Handler) GetDeliveryCount(c *gin.Context) {
|
||||||
|
counts, err := h.queries.GetDeliveryCount(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get delivery count", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"counts": counts})
|
||||||
|
}
|
||||||
|
|
||||||
// POST /api/deliveries
|
// POST /api/deliveries
|
||||||
func (h *Handler) CreateDelivery(c *gin.Context) {
|
func (h *Handler) CreateDelivery(c *gin.Context) {
|
||||||
var req DeliveryRequest = DeliveryRequest{}
|
var req DeliveryRequest = DeliveryRequest{}
|
||||||
@@ -149,6 +160,47 @@ func (h *Handler) UpdateDelivery(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"message": "Delivery updated"})
|
c.JSON(http.StatusOK, gin.H{"message": "Delivery updated"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PATCH /api/deliveries/:id/status
|
||||||
|
func (h *Handler) UpdateDeliveryStatus(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "ID is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedID, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid UUID format", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := req.Status
|
||||||
|
if status == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Status is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.queries.UpdateDeliveryStatus(c.Request.Context(), sqlc.UpdateDeliveryStatusParams{
|
||||||
|
ID: pgtype.UUID{Bytes: parsedID, Valid: true},
|
||||||
|
Status: status,
|
||||||
|
}); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update delivery status", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "Delivery status updated"})
|
||||||
|
}
|
||||||
|
|
||||||
// DELETE /api/deliveries/:id
|
// DELETE /api/deliveries/:id
|
||||||
func (h *Handler) DeleteDelivery(c *gin.Context) {
|
func (h *Handler) DeleteDelivery(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
|
|||||||
@@ -1,4 +1,36 @@
|
|||||||
services:
|
services:
|
||||||
|
# PostgreSQL database
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: egor
|
||||||
|
POSTGRES_PASSWORD: barsik
|
||||||
|
POSTGRES_DB: delivery_tracker
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U egor -d delivery_tracker"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# Backend API
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: production
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgres://egor:barsik@postgres:5432/delivery_tracker?sslmode=disable
|
||||||
|
ports:
|
||||||
|
- "8081:8080"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
# Development service with hot reload
|
# Development service with hot reload
|
||||||
frontend-dev:
|
frontend-dev:
|
||||||
image: node:20-alpine
|
image: node:20-alpine
|
||||||
@@ -22,7 +54,10 @@ services:
|
|||||||
target: production
|
target: production
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
node_modules:
|
node_modules:
|
||||||
|
postgres_data:
|
||||||
|
|||||||
Reference in New Issue
Block a user