feat: add container management functionality

This commit is contained in:
Tran Anh Tuan
2026-05-11 09:42:19 +07:00
parent 9f27436d5d
commit 7c9a0d4670
15 changed files with 1654 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ const (
API_GROUP_ROOM = "/rooms" API_GROUP_ROOM = "/rooms"
API_GROUP_CABINET = "/cabinets" API_GROUP_CABINET = "/cabinets"
API_GROUP_SHELF = "/shelves" API_GROUP_SHELF = "/shelves"
API_GROUP_CONTAINER = "/containers"
) )
const ( const (

35
db/queries/container.sql Normal file
View File

@@ -0,0 +1,35 @@
-- name: GetContainerByID :one
SELECT * FROM containers
WHERE id = sqlc.arg(id);
-- name: ListContainers :many
SELECT * FROM containers
ORDER BY created_at DESC;
-- name: CreateContainer :one
INSERT INTO containers (shelf_id,name,container_type, description,max_capacity,metadata, created_at)
VALUES (
sqlc.arg(shelf_id),
sqlc.arg(name),
sqlc.arg(container_type),
sqlc.arg(description),
sqlc.arg(max_capacity),
sqlc.arg(metadata),
sqlc.arg(created_at)
)
RETURNING *;
-- name: UpdateContainer :one
UPDATE containers
SET name = CASE WHEN sqlc.arg(name) = '' THEN name ELSE sqlc.arg(name) END,
description = coalesce(sqlc.arg(description), description),
container_type = coalesce(sqlc.narg(container_type), container_type),
max_capacity = coalesce(sqlc.arg(max_capacity), max_capacity),
metadata = coalesce(sqlc.arg(metadata), metadata),
updated_at = sqlc.arg(updated_at)
WHERE id = sqlc.arg(id)
RETURNING *;
-- name: DeleteContainer :execrows
DELETE FROM containers
WHERE id = sqlc.arg(id);

View File

@@ -378,6 +378,279 @@ const docTemplate = `{
} }
} }
}, },
"/v1/containers": {
"get": {
"description": "Retrieve a list of all containers ordered by creation date",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "List all containers",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Container"
}
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
},
"post": {
"description": "Create a new container with the provided details",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Create a new container",
"parameters": [
{
"description": "Container request body",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.CreateContainerRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/responses.CreateContainerResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/containers/{id}": {
"get": {
"description": "Retrieve a single container using its unique identifier",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Get container by ID",
"parameters": [
{
"type": "integer",
"description": "Container ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Container"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
},
"put": {
"description": "Update an existing container by its ID. Only non-empty fields will be updated.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Update container",
"parameters": [
{
"type": "integer",
"description": "Container ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Container request body",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.UpdateContainerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/responses.UpdateContainerResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
},
"delete": {
"description": "Delete a container by its unique identifier",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Delete container",
"parameters": [
{
"type": "integer",
"description": "Container ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.SuccessResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/rooms": { "/v1/rooms": {
"get": { "get": {
"description": "Retrieve a list of all rooms ordered by creation date", "description": "Retrieve a list of all rooms ordered by creation date",
@@ -1216,6 +1489,41 @@ const docTemplate = `{
} }
} }
}, },
"models.Container": {
"type": "object",
"properties": {
"containerType": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"id": {
"type": "integer"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"shelfId": {
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
},
"models.Room": { "models.Room": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1329,6 +1637,37 @@ const docTemplate = `{
} }
} }
}, },
"requests.CreateContainerRequest": {
"type": "object",
"required": [
"containerType",
"name",
"shelfId"
],
"properties": {
"containerType": {
"type": "string"
},
"description": {
"type": "string"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"shelfId": {
"type": "integer"
}
}
},
"requests.CreateRoomRequest": { "requests.CreateRoomRequest": {
"type": "object", "type": "object",
"required": [ "required": [
@@ -1398,6 +1737,29 @@ const docTemplate = `{
} }
} }
}, },
"requests.UpdateContainerRequest": {
"type": "object",
"properties": {
"containerType": {
"type": "string"
},
"description": {
"type": "string"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
}
}
},
"requests.UpdateRoomRequest": { "requests.UpdateRoomRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1486,6 +1848,14 @@ const docTemplate = `{
} }
} }
}, },
"responses.CreateContainerResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer"
}
}
},
"responses.CreateRoomResponse": { "responses.CreateRoomResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1527,6 +1897,35 @@ const docTemplate = `{
} }
} }
}, },
"responses.UpdateContainerResponse": {
"type": "object",
"properties": {
"containerType": {
"type": "string"
},
"description": {
"type": "string"
},
"id": {
"type": "integer"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"shelfId": {
"type": "integer"
}
}
},
"responses.UpdateRoomResponse": { "responses.UpdateRoomResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -372,6 +372,279 @@
} }
} }
}, },
"/v1/containers": {
"get": {
"description": "Retrieve a list of all containers ordered by creation date",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "List all containers",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/models.Container"
}
}
}
}
]
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
},
"post": {
"description": "Create a new container with the provided details",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Create a new container",
"parameters": [
{
"description": "Container request body",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.CreateContainerRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/responses.CreateContainerResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/containers/{id}": {
"get": {
"description": "Retrieve a single container using its unique identifier",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Get container by ID",
"parameters": [
{
"type": "integer",
"description": "Container ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/models.Container"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
},
"put": {
"description": "Update an existing container by its ID. Only non-empty fields will be updated.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Update container",
"parameters": [
{
"type": "integer",
"description": "Container ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Container request body",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/requests.UpdateContainerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.SuccessResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/responses.UpdateContainerResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
},
"delete": {
"description": "Delete a container by its unique identifier",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"container"
],
"summary": "Delete container",
"parameters": [
{
"type": "integer",
"description": "Container ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.SuccessResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.ErrorResponse"
}
}
}
}
},
"/v1/rooms": { "/v1/rooms": {
"get": { "get": {
"description": "Retrieve a list of all rooms ordered by creation date", "description": "Retrieve a list of all rooms ordered by creation date",
@@ -1210,6 +1483,41 @@
} }
} }
}, },
"models.Container": {
"type": "object",
"properties": {
"containerType": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"id": {
"type": "integer"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"shelfId": {
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
},
"models.Room": { "models.Room": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1323,6 +1631,37 @@
} }
} }
}, },
"requests.CreateContainerRequest": {
"type": "object",
"required": [
"containerType",
"name",
"shelfId"
],
"properties": {
"containerType": {
"type": "string"
},
"description": {
"type": "string"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"shelfId": {
"type": "integer"
}
}
},
"requests.CreateRoomRequest": { "requests.CreateRoomRequest": {
"type": "object", "type": "object",
"required": [ "required": [
@@ -1392,6 +1731,29 @@
} }
} }
}, },
"requests.UpdateContainerRequest": {
"type": "object",
"properties": {
"containerType": {
"type": "string"
},
"description": {
"type": "string"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
}
}
},
"requests.UpdateRoomRequest": { "requests.UpdateRoomRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1480,6 +1842,14 @@
} }
} }
}, },
"responses.CreateContainerResponse": {
"type": "object",
"properties": {
"id": {
"type": "integer"
}
}
},
"responses.CreateRoomResponse": { "responses.CreateRoomResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1521,6 +1891,35 @@
} }
} }
}, },
"responses.UpdateContainerResponse": {
"type": "object",
"properties": {
"containerType": {
"type": "string"
},
"description": {
"type": "string"
},
"id": {
"type": "integer"
},
"maxCapacity": {
"type": "integer"
},
"metadata": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"shelfId": {
"type": "integer"
}
}
},
"responses.UpdateRoomResponse": { "responses.UpdateRoomResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -15,6 +15,29 @@ definitions:
updatedAt: updatedAt:
type: string type: string
type: object type: object
models.Container:
properties:
containerType:
type: string
createdAt:
type: string
description:
type: string
id:
type: integer
maxCapacity:
type: integer
metadata:
items:
type: integer
type: array
name:
type: string
shelfId:
type: integer
updatedAt:
type: string
type: object
models.Room: models.Room:
properties: properties:
createdAt: createdAt:
@@ -90,6 +113,27 @@ definitions:
- name - name
- roomId - roomId
type: object type: object
requests.CreateContainerRequest:
properties:
containerType:
type: string
description:
type: string
maxCapacity:
type: integer
metadata:
items:
type: integer
type: array
name:
type: string
shelfId:
type: integer
required:
- containerType
- name
- shelfId
type: object
requests.CreateRoomRequest: requests.CreateRoomRequest:
properties: properties:
description: description:
@@ -136,6 +180,21 @@ definitions:
name: name:
type: string type: string
type: object type: object
requests.UpdateContainerRequest:
properties:
containerType:
type: string
description:
type: string
maxCapacity:
type: integer
metadata:
items:
type: integer
type: array
name:
type: string
type: object
requests.UpdateRoomRequest: requests.UpdateRoomRequest:
properties: properties:
description: description:
@@ -193,6 +252,11 @@ definitions:
id: id:
type: integer type: integer
type: object type: object
responses.CreateContainerResponse:
properties:
id:
type: integer
type: object
responses.CreateRoomResponse: responses.CreateRoomResponse:
properties: properties:
id: id:
@@ -219,6 +283,25 @@ definitions:
roomId: roomId:
type: integer type: integer
type: object type: object
responses.UpdateContainerResponse:
properties:
containerType:
type: string
description:
type: string
id:
type: integer
maxCapacity:
type: integer
metadata:
items:
type: integer
type: array
name:
type: string
shelfId:
type: integer
type: object
responses.UpdateRoomResponse: responses.UpdateRoomResponse:
properties: properties:
description: description:
@@ -487,6 +570,176 @@ paths:
summary: Update cabinet summary: Update cabinet
tags: tags:
- cabinet - cabinet
/v1/containers:
get:
consumes:
- application/json
description: Retrieve a list of all containers ordered by creation date
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.SuccessResponse'
- properties:
data:
items:
$ref: '#/definitions/models.Container'
type: array
type: object
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: List all containers
tags:
- container
post:
consumes:
- application/json
description: Create a new container with the provided details
parameters:
- description: Container request body
in: body
name: body
required: true
schema:
$ref: '#/definitions/requests.CreateContainerRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
allOf:
- $ref: '#/definitions/response.SuccessResponse'
- properties:
data:
$ref: '#/definitions/responses.CreateContainerResponse'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: Create a new container
tags:
- container
/v1/containers/{id}:
delete:
consumes:
- application/json
description: Delete a container by its unique identifier
parameters:
- description: Container ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.SuccessResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: Delete container
tags:
- container
get:
consumes:
- application/json
description: Retrieve a single container using its unique identifier
parameters:
- description: Container ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.SuccessResponse'
- properties:
data:
$ref: '#/definitions/models.Container'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/response.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: Get container by ID
tags:
- container
put:
consumes:
- application/json
description: Update an existing container by its ID. Only non-empty fields will
be updated.
parameters:
- description: Container ID
in: path
name: id
required: true
type: integer
- description: Container request body
in: body
name: body
required: true
schema:
$ref: '#/definitions/requests.UpdateContainerRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.SuccessResponse'
- properties:
data:
$ref: '#/definitions/responses.UpdateContainerResponse'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/response.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.ErrorResponse'
summary: Update container
tags:
- container
/v1/rooms: /v1/rooms:
get: get:
consumes: consumes:

View File

@@ -0,0 +1,61 @@
package mapper
import (
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/jackc/pgx/v5/pgtype"
)
func ToDomainContainer(r db.Container) *models.Container {
return &models.Container{
ID: r.ID,
ShelfID: r.ShelfID,
Name: r.Name,
ContainerType: string(r.ContainerType),
Description: r.Description.String,
MaxCapacity: r.MaxCapacity.Int32,
Metadata: r.Metadata,
CreatedAt: r.CreatedAt,
UpdatedAt: r.UpdatedAt,
}
}
func ToModelContainer(r *models.Container) *db.CreateContainerParams {
return &db.CreateContainerParams{
ShelfID: r.ShelfID,
Name: r.Name,
ContainerType: db.ContainerTypeEnum(r.ContainerType),
Description: pgtype.Text{
String: r.Description,
Valid: r.Description != "",
},
MaxCapacity: pgtype.Int4{
Int32: r.MaxCapacity,
Valid: r.MaxCapacity != 0,
},
Metadata: r.Metadata,
CreatedAt: r.CreatedAt,
}
}
func ToUpdateModelContainer(r *models.Container) *db.UpdateContainerParams {
return &db.UpdateContainerParams{
Name: r.Name,
ContainerType: db.NullContainerTypeEnum{
ContainerTypeEnum: db.ContainerTypeEnum(r.ContainerType),
Valid: r.ContainerType != "",
},
Description: pgtype.Text{
String: r.Description,
Valid: r.Description != "",
},
MaxCapacity: pgtype.Int4{
Int32: r.MaxCapacity,
Valid: r.MaxCapacity != 0,
},
Metadata: r.Metadata,
UpdatedAt: r.UpdatedAt,
ID: r.ID,
}
}

View File

@@ -0,0 +1,18 @@
package models
import (
"encoding/json"
"time"
)
type Container struct {
ID int64 `json:"id"`
ShelfID int64 `json:"shelfId"`
Name string `json:"name"`
ContainerType string `json:"containerType"`
Description string `json:"description"`
MaxCapacity int32 `json:"maxCapacity"`
Metadata json.RawMessage `json:"metadata"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}

View File

@@ -0,0 +1,20 @@
package requests
import "encoding/json"
type CreateContainerRequest struct {
ShelfID int64 `json:"shelfId" binding:"required"`
Name string `json:"name" binding:"required"`
ContainerType string `json:"containerType" binding:"required"`
Description string `json:"description"`
MaxCapacity int32 `json:"maxCapacity"`
Metadata json.RawMessage `json:"metadata"`
}
type UpdateContainerRequest struct {
Name string `json:"name"`
ContainerType string `json:"containerType"`
Description string `json:"description"`
MaxCapacity int32 `json:"maxCapacity"`
Metadata json.RawMessage `json:"metadata"`
}

View File

@@ -0,0 +1,17 @@
package responses
import "encoding/json"
type CreateContainerResponse struct {
ID int64 `json:"id"`
}
type UpdateContainerResponse struct {
ID int64 `json:"id"`
ShelfID int64 `json:"shelfId"`
Name string `json:"name"`
ContainerType string `json:"containerType"`
Description string `json:"description"`
MaxCapacity int32 `json:"maxCapacity"`
Metadata json.RawMessage `json:"metadata"`
}

View File

@@ -0,0 +1,55 @@
package repositories
import (
"context"
"wm-backend/internal/mapper"
"wm-backend/internal/models"
db "wm-backend/sqlc_gen"
"github.com/rs/zerolog/log"
)
func CreateContainer(ctx context.Context, queries *db.Queries, body models.Container) (models.Container, error) {
result, err := queries.CreateContainer(ctx, *mapper.ToModelContainer(&body))
if err != nil {
return models.Container{}, err
}
return *mapper.ToDomainContainer(result), nil
}
func GetContainerByID(ctx context.Context, queries *db.Queries, id int64) (models.Container, error) {
result, err := queries.GetContainerByID(ctx, id)
if err != nil {
return models.Container{}, err
}
return *mapper.ToDomainContainer(result), nil
}
func ListContainers(ctx context.Context, queries *db.Queries) ([]models.Container, error) {
results, err := queries.ListContainers(ctx)
if err != nil {
return nil, err
}
var items []models.Container
for _, r := range results {
items = append(items, *mapper.ToDomainContainer(r))
}
return items, nil
}
func UpdateContainer(ctx context.Context, queries *db.Queries, body models.Container) (models.Container, error) {
result, err := queries.UpdateContainer(ctx, *mapper.ToUpdateModelContainer(&body))
if err != nil {
return models.Container{}, err
}
return *mapper.ToDomainContainer(result), nil
}
func DeleteContainer(ctx context.Context, queries *db.Queries, id int64) (int64, error) {
rowsAffected, err := queries.DeleteContainer(ctx, id)
log.Info().Int64("id", id).Int64("rowsAffected", rowsAffected).Msg("Deleted container")
if err != nil {
return rowsAffected, err
}
return rowsAffected, nil
}

View File

@@ -64,6 +64,15 @@ func NewRouter() *gin.Engine {
shelve.PUT("/:id", utils.AsyncHandler(services.ShelveUpdate)) shelve.PUT("/:id", utils.AsyncHandler(services.ShelveUpdate))
shelve.DELETE("/:id", utils.AsyncHandler(services.ShelveDelete)) shelve.DELETE("/:id", utils.AsyncHandler(services.ShelveDelete))
} }
container := v1.Group(constants.API_GROUP_CONTAINER)
{
container.GET("", utils.AsyncHandler(services.ContainerList))
container.GET("/:id", utils.AsyncHandler(services.ContainerGetByID))
container.POST("", utils.AsyncHandler(services.ContainerCreate))
container.PUT("/:id", utils.AsyncHandler(services.ContainerUpdate))
container.DELETE("/:id", utils.AsyncHandler(services.ContainerDelete))
}
} }
r.GET(constants.API_PATH_PING, services.PingHandler) r.GET(constants.API_PATH_PING, services.PingHandler)

View File

@@ -0,0 +1,202 @@
package services
import (
"net/http"
"strconv"
"time"
"wm-backend/global"
"wm-backend/internal/models"
"wm-backend/internal/models/requests"
"wm-backend/internal/models/responses"
"wm-backend/internal/repositories"
"wm-backend/pkg/helper"
"wm-backend/response"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
// ContainerCreate creates a new container.
// It validates the request body and creates the container in the database.
//
// @Summary Create a new container
// @Description Create a new container with the provided details
// @Tags container
// @Accept json
// @Produce json
// @Param body body requests.CreateContainerRequest true "Container request body"
// @Success 201 {object} response.SuccessResponse{data=responses.CreateContainerResponse}
// @Failure 400 {object} response.ErrorResponse
// @Failure 500 {object} response.ErrorResponse
// @Router /v1/containers [post]
func ContainerCreate(c *gin.Context) error {
requestBody := requests.CreateContainerRequest{}
if helper.IsShouldBindJSON(c, &requestBody) {
return nil
}
containerModel := &models.Container{
ShelfID: requestBody.ShelfID,
Name: requestBody.Name,
ContainerType: requestBody.ContainerType,
Description: requestBody.Description,
MaxCapacity: requestBody.MaxCapacity,
Metadata: requestBody.Metadata,
CreatedAt: time.Now(),
}
container, err := repositories.CreateContainer(c.Request.Context(), global.Queries, *containerModel)
if err != nil {
log.Error().Err(err).Msg("Failed to create container")
response.InternalServerError(c, http.StatusInternalServerError, "Failed to create container")
return nil
}
response.Created(c, "Container created successfully", &responses.CreateContainerResponse{
ID: container.ID,
})
return nil
}
// ContainerGetByID retrieves a single container by its ID.
//
// @Summary Get container by ID
// @Description Retrieve a single container using its unique identifier
// @Tags container
// @Accept json
// @Produce json
// @Param id path int true "Container ID"
// @Success 200 {object} response.SuccessResponse{data=models.Container}
// @Failure 400 {object} response.ErrorResponse
// @Failure 404 {object} response.ErrorResponse
// @Failure 500 {object} response.ErrorResponse
// @Router /v1/containers/{id} [get]
func ContainerGetByID(c *gin.Context) error {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequestError(c, http.StatusBadRequest, "Invalid ID")
return nil
}
container, err := repositories.GetContainerByID(c.Request.Context(), global.Queries, id)
if err != nil {
log.Error().Err(err).Msgf("Failed to get container by ID: %d", id)
response.NotFoundError(c, http.StatusNotFound, "Container not found")
return nil
}
response.Ok(c, "Success", container)
return nil
}
// ContainerList retrieves all containers.
//
// @Summary List all containers
// @Description Retrieve a list of all containers ordered by creation date
// @Tags container
// @Accept json
// @Produce json
// @Success 200 {object} response.SuccessResponse{data=[]models.Container}
// @Failure 500 {object} response.ErrorResponse
// @Router /v1/containers [get]
func ContainerList(c *gin.Context) error {
containers, err := repositories.ListContainers(c.Request.Context(), global.Queries)
if err != nil {
response.InternalServerError(c, http.StatusInternalServerError, "Failed to list containers")
return nil
}
response.Ok(c, "Success", containers)
return nil
}
// ContainerUpdate updates an existing container by its ID.
// It validates the request body, fetches the existing record,
// merges non-empty fields from the request, and updates the container in the database.
//
// @Summary Update container
// @Description Update an existing container by its ID. Only non-empty fields will be updated.
// @Tags container
// @Accept json
// @Produce json
// @Param id path int true "Container ID"
// @Param body body requests.UpdateContainerRequest true "Container request body"
// @Success 200 {object} response.SuccessResponse{data=responses.UpdateContainerResponse}
// @Failure 400 {object} response.ErrorResponse
// @Failure 404 {object} response.ErrorResponse
// @Failure 500 {object} response.ErrorResponse
// @Router /v1/containers/{id} [put]
func ContainerUpdate(c *gin.Context) error {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequestError(c, http.StatusBadRequest, "Invalid ID")
return nil
}
requestBody := requests.UpdateContainerRequest{}
if helper.IsShouldBindJSON(c, &requestBody) {
return nil
}
existing, err := repositories.GetContainerByID(c.Request.Context(), global.Queries, id)
if err != nil {
response.NotFoundError(c, http.StatusNotFound, "Container not found")
return nil
}
if requestBody.Name != "" {
existing.Name = requestBody.Name
}
if requestBody.ContainerType != "" {
existing.ContainerType = requestBody.ContainerType
}
if requestBody.Description != "" {
existing.Description = requestBody.Description
}
if requestBody.MaxCapacity != 0 {
existing.MaxCapacity = requestBody.MaxCapacity
}
if len(requestBody.Metadata) > 0 {
existing.Metadata = requestBody.Metadata
}
existing.UpdatedAt = time.Now()
container, err := repositories.UpdateContainer(c.Request.Context(), global.Queries, existing)
if err != nil {
log.Error().Err(err).Msgf("Failed to update container with ID: %d", id)
response.InternalServerError(c, http.StatusInternalServerError, "Failed to update container")
return nil
}
response.Ok(c, "Container updated successfully", &responses.UpdateContainerResponse{
ID: container.ID,
ShelfID: container.ShelfID,
Name: container.Name,
ContainerType: container.ContainerType,
Description: container.Description,
MaxCapacity: container.MaxCapacity,
Metadata: container.Metadata,
})
return nil
}
// ContainerDelete deletes a container by its ID.
//
// @Summary Delete container
// @Description Delete a container by its unique identifier
// @Tags container
// @Accept json
// @Produce json
// @Param id path int true "Container ID"
// @Success 200 {object} response.SuccessResponse
// @Failure 400 {object} response.ErrorResponse
// @Failure 500 {object} response.ErrorResponse
// @Router /v1/containers/{id} [delete]
func ContainerDelete(c *gin.Context) error {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
response.BadRequestError(c, http.StatusBadRequest, "Invalid ID")
return nil
}
rowsAffected, err := repositories.DeleteContainer(c.Request.Context(), global.Queries, id)
if err != nil {
log.Error().Err(err).Msgf("Failed to delete container with ID: %d", id)
response.InternalServerError(c, http.StatusInternalServerError, "Failed to delete container")
return nil
}
if rowsAffected == 0 {
response.NotFoundError(c, http.StatusNotFound, "Container not found")
return nil
}
response.Ok(c, "Delete Success", nil)
return nil
}

View File

@@ -43,7 +43,7 @@ func ShelveCreate(c *gin.Context) error {
} }
shelve, err := repositories.CreateShelve(c.Request.Context(), global.Queries, *shelveModel) shelve, err := repositories.CreateShelve(c.Request.Context(), global.Queries, *shelveModel)
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to create shelve") log.Error().Any("shelve", shelve).Err(err).Msg("Failed to create shelve")
response.InternalServerError(c, http.StatusInternalServerError, "Failed to create shelve") response.InternalServerError(c, http.StatusInternalServerError, "Failed to create shelve")
return nil return nil
} }

179
sqlc_gen/container.sql.go Normal file
View File

@@ -0,0 +1,179 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: container.sql
package db
import (
"context"
"time"
"github.com/jackc/pgx/v5/pgtype"
)
const createContainer = `-- name: CreateContainer :one
INSERT INTO containers (shelf_id,name,container_type, description,max_capacity,metadata, created_at)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)
RETURNING id, shelf_id, name, container_type, description, max_capacity, metadata, created_at, updated_at
`
type CreateContainerParams struct {
ShelfID int64 `db:"shelf_id" json:"shelfId"`
Name string `db:"name" json:"name"`
ContainerType ContainerTypeEnum `db:"container_type" json:"containerType"`
Description pgtype.Text `db:"description" json:"description"`
MaxCapacity pgtype.Int4 `db:"max_capacity" json:"maxCapacity"`
Metadata []byte `db:"metadata" json:"metadata"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
}
func (q *Queries) CreateContainer(ctx context.Context, arg CreateContainerParams) (Container, error) {
row := q.db.QueryRow(ctx, createContainer,
arg.ShelfID,
arg.Name,
arg.ContainerType,
arg.Description,
arg.MaxCapacity,
arg.Metadata,
arg.CreatedAt,
)
var i Container
err := row.Scan(
&i.ID,
&i.ShelfID,
&i.Name,
&i.ContainerType,
&i.Description,
&i.MaxCapacity,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteContainer = `-- name: DeleteContainer :execrows
DELETE FROM containers
WHERE id = $1
`
func (q *Queries) DeleteContainer(ctx context.Context, id int64) (int64, error) {
result, err := q.db.Exec(ctx, deleteContainer, id)
if err != nil {
return 0, err
}
return result.RowsAffected(), nil
}
const getContainerByID = `-- name: GetContainerByID :one
SELECT id, shelf_id, name, container_type, description, max_capacity, metadata, created_at, updated_at FROM containers
WHERE id = $1
`
func (q *Queries) GetContainerByID(ctx context.Context, id int64) (Container, error) {
row := q.db.QueryRow(ctx, getContainerByID, id)
var i Container
err := row.Scan(
&i.ID,
&i.ShelfID,
&i.Name,
&i.ContainerType,
&i.Description,
&i.MaxCapacity,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listContainers = `-- name: ListContainers :many
SELECT id, shelf_id, name, container_type, description, max_capacity, metadata, created_at, updated_at FROM containers
ORDER BY created_at DESC
`
func (q *Queries) ListContainers(ctx context.Context) ([]Container, error) {
rows, err := q.db.Query(ctx, listContainers)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Container
for rows.Next() {
var i Container
if err := rows.Scan(
&i.ID,
&i.ShelfID,
&i.Name,
&i.ContainerType,
&i.Description,
&i.MaxCapacity,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateContainer = `-- name: UpdateContainer :one
UPDATE containers
SET name = CASE WHEN $1 = '' THEN name ELSE $1 END,
description = coalesce($2, description),
container_type = coalesce($3, container_type),
max_capacity = coalesce($4, max_capacity),
metadata = coalesce($5, metadata),
updated_at = $6
WHERE id = $7
RETURNING id, shelf_id, name, container_type, description, max_capacity, metadata, created_at, updated_at
`
type UpdateContainerParams struct {
Name interface{} `db:"name" json:"name"`
Description pgtype.Text `db:"description" json:"description"`
ContainerType NullContainerTypeEnum `db:"container_type" json:"containerType"`
MaxCapacity pgtype.Int4 `db:"max_capacity" json:"maxCapacity"`
Metadata []byte `db:"metadata" json:"metadata"`
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
ID int64 `db:"id" json:"id"`
}
func (q *Queries) UpdateContainer(ctx context.Context, arg UpdateContainerParams) (Container, error) {
row := q.db.QueryRow(ctx, updateContainer,
arg.Name,
arg.Description,
arg.ContainerType,
arg.MaxCapacity,
arg.Metadata,
arg.UpdatedAt,
arg.ID,
)
var i Container
err := row.Scan(
&i.ID,
&i.ShelfID,
&i.Name,
&i.ContainerType,
&i.Description,
&i.MaxCapacity,
&i.Metadata,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@@ -14,17 +14,20 @@ type Querier interface {
AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error) AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error)
CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error) CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error)
CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error) CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error)
CreateContainer(ctx context.Context, arg CreateContainerParams) (Container, error)
CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error) CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error)
CreateRoom(ctx context.Context, arg CreateRoomParams) (Room, error) CreateRoom(ctx context.Context, arg CreateRoomParams) (Room, error)
CreateShelve(ctx context.Context, arg CreateShelveParams) (Shelf, error) CreateShelve(ctx context.Context, arg CreateShelveParams) (Shelf, error)
CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error) CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error)
CreateWarehouse(ctx context.Context, arg CreateWarehouseParams) (Warehouse, error) CreateWarehouse(ctx context.Context, arg CreateWarehouseParams) (Warehouse, error)
DeleteCabinet(ctx context.Context, id int64) (int64, error) DeleteCabinet(ctx context.Context, id int64) (int64, error)
DeleteContainer(ctx context.Context, id int64) (int64, error)
DeleteRole(ctx context.Context, id uuid.UUID) (int64, error) DeleteRole(ctx context.Context, id uuid.UUID) (int64, error)
DeleteRoom(ctx context.Context, id int64) (int64, error) DeleteRoom(ctx context.Context, id int64) (int64, error)
DeleteShelve(ctx context.Context, id int64) (int64, error) DeleteShelve(ctx context.Context, id int64) (int64, error)
DeleteWarehouse(ctx context.Context, id int64) (int64, error) DeleteWarehouse(ctx context.Context, id int64) (int64, error)
GetCabinetByID(ctx context.Context, id int64) (Cabinet, error) GetCabinetByID(ctx context.Context, id int64) (Cabinet, error)
GetContainerByID(ctx context.Context, id int64) (Container, error)
GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error) GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error)
GetRoomByID(ctx context.Context, id int64) (Room, error) GetRoomByID(ctx context.Context, id int64) (Room, error)
GetShelveByID(ctx context.Context, id int64) (Shelf, error) GetShelveByID(ctx context.Context, id int64) (Shelf, error)
@@ -36,6 +39,7 @@ type Querier interface {
GetUserRolesByUserID(ctx context.Context, userID uuid.UUID) ([]GetUserRolesByUserIDRow, error) GetUserRolesByUserID(ctx context.Context, userID uuid.UUID) ([]GetUserRolesByUserIDRow, error)
GetWarehouseByID(ctx context.Context, id int64) (Warehouse, error) GetWarehouseByID(ctx context.Context, id int64) (Warehouse, error)
ListCabinets(ctx context.Context) ([]Cabinet, error) ListCabinets(ctx context.Context) ([]Cabinet, error)
ListContainers(ctx context.Context) ([]Container, error)
ListRoles(ctx context.Context) ([]Role, error) ListRoles(ctx context.Context) ([]Role, error)
ListRooms(ctx context.Context) ([]Room, error) ListRooms(ctx context.Context) ([]Room, error)
ListShelves(ctx context.Context) ([]Shelf, error) ListShelves(ctx context.Context) ([]Shelf, error)
@@ -43,6 +47,7 @@ type Querier interface {
RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error
RemoveRoleFromUser(ctx context.Context, arg RemoveRoleFromUserParams) error RemoveRoleFromUser(ctx context.Context, arg RemoveRoleFromUserParams) error
UpdateCabinet(ctx context.Context, arg UpdateCabinetParams) (Cabinet, error) UpdateCabinet(ctx context.Context, arg UpdateCabinetParams) (Cabinet, error)
UpdateContainer(ctx context.Context, arg UpdateContainerParams) (Container, error)
UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error) UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error)
UpdateRoom(ctx context.Context, arg UpdateRoomParams) (Room, error) UpdateRoom(ctx context.Context, arg UpdateRoomParams) (Room, error)
UpdateShelve(ctx context.Context, arg UpdateShelveParams) (Shelf, error) UpdateShelve(ctx context.Context, arg UpdateShelveParams) (Shelf, error)