Compare commits

..

3 Commits

Author SHA1 Message Date
Egor Pozharov
b36a6fb262 add PATCH update delivery status by id 2026-04-14 15:46:32 +06:00
Egor Pozharov
d3cd92b9f3 add dockerfile for backend && update docker-compose.yml 2026-04-14 15:39:21 +06:00
Egor Pozharov
10233808f4 add GET delivery count route 2026-04-14 15:38:45 +06:00
7 changed files with 175 additions and 1 deletions

View File

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

View File

@@ -35,8 +35,10 @@ func main() {
r.GET("/api/deliveries", h.GetDeliveries)
r.GET("/api/deliveries/:id", h.GetDeliveryByID)
r.GET("/api/deliveries/count", h.GetDeliveryCount)
r.POST("/api/deliveries", h.CreateDelivery)
r.PATCH("/api/deliveries/:id", h.UpdateDelivery)
r.PATCH("/api/deliveries/:id/status", h.UpdateDeliveryStatus)
r.DELETE("/api/deliveries/:id", h.DeleteDelivery)
r.Run(":8080")

View File

@@ -13,4 +13,10 @@ SELECT * FROM deliveries WHERE id = $1;
DELETE FROM deliveries WHERE id = $1;
-- 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;

View File

@@ -15,7 +15,9 @@ type Querier interface {
DeleteDelivery(ctx context.Context, id pgtype.UUID) error
GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([]Delivery, error)
GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error)
GetDeliveryCount(ctx context.Context) ([]GetDeliveryCountRow, error)
UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) error
UpdateDeliveryStatus(ctx context.Context, arg UpdateDeliveryStatusParams) error
}
var _ Querier = (*Queries)(nil)

View File

@@ -127,6 +127,35 @@ func (q *Queries) GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery
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
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
}
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
}

View File

@@ -69,6 +69,17 @@ func (h *Handler) GetDeliveries(c *gin.Context) {
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
func (h *Handler) CreateDelivery(c *gin.Context) {
var req DeliveryRequest = DeliveryRequest{}
@@ -149,6 +160,47 @@ func (h *Handler) UpdateDelivery(c *gin.Context) {
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
func (h *Handler) DeleteDelivery(c *gin.Context) {
id := c.Param("id")

View File

@@ -1,4 +1,36 @@
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
frontend-dev:
image: node:20-alpine
@@ -22,7 +54,10 @@ services:
target: production
ports:
- "8080:80"
depends_on:
- backend
restart: unless-stopped
volumes:
node_modules:
postgres_data: