diff --git a/configs/constants/constants.go b/configs/constants/constants.go index 8c56499..2e25098 100644 --- a/configs/constants/constants.go +++ b/configs/constants/constants.go @@ -15,7 +15,8 @@ const ( API_GROUP_ROOM = "/rooms" API_GROUP_CABINET = "/cabinets" API_GROUP_SHELF = "/shelves" - API_GROUP_CONTAINER = "/containers" + API_GROUP_CONTAINER = "/containers" + API_GROUP_COMPONENT_TYPE = "/component-types" ) const ( diff --git a/db/queries/component_type.sql b/db/queries/component_type.sql new file mode 100644 index 0000000..4b43554 --- /dev/null +++ b/db/queries/component_type.sql @@ -0,0 +1,30 @@ +-- name: GetComponentTypeByID :one +SELECT * FROM component_types +WHERE id = sqlc.arg(id); + +-- name: ListComponentTypes :many +SELECT * FROM component_types +ORDER BY created_at DESC; + +-- name: CreateComponentType :one +INSERT INTO component_types (name, description,metadata, created_at) +VALUES ( + sqlc.arg(name), + sqlc.arg(description), + sqlc.arg(metadata), + sqlc.arg(created_at) +) +RETURNING *; + +-- name: UpdateComponentType :one +UPDATE component_types +SET name = CASE WHEN sqlc.arg(name) = '' THEN name ELSE sqlc.arg(name) END, + description = coalesce(sqlc.arg(description), description), + metadata = coalesce(sqlc.arg(metadata), metadata), + updated_at = sqlc.arg(updated_at) +WHERE id = sqlc.arg(id) +RETURNING *; + +-- name: DeleteComponentType :execrows +DELETE FROM component_types +WHERE id = sqlc.arg(id); diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index d31920f..c896d56 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -15,6 +15,279 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/api/v1/component-types": { + "get": { + "description": "Retrieve a list of all component types ordered by creation date", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "List all component types", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ComponentType" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new component type with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Create a new component type", + "parameters": [ + { + "description": "Component type request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CreateComponentTypeRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.CreateComponentTypeResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/api/v1/component-types/{id}": { + "get": { + "description": "Retrieve a single component type using its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Get component type by ID", + "parameters": [ + { + "type": "integer", + "description": "Component type ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.ComponentType" + } + } + } + ] + } + }, + "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 component type by its ID. Only non-empty fields will be updated.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Update component type", + "parameters": [ + { + "type": "integer", + "description": "Component type ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Component type request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UpdateComponentTypeRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.UpdateComponentTypeResponse" + } + } + } + ] + } + }, + "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 component type by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Delete component type", + "parameters": [ + { + "type": "integer", + "description": "Component type 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" + } + } + } + } + }, "/auth/register": { "post": { "description": "Register with email, username and password", @@ -1489,6 +1762,32 @@ const docTemplate = `{ } } }, + "models.ComponentType": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "models.Container": { "type": "object", "properties": { @@ -1637,6 +1936,26 @@ const docTemplate = `{ } } }, + "requests.CreateComponentTypeRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string" + } + } + }, "requests.CreateContainerRequest": { "type": "object", "required": [ @@ -1737,6 +2056,23 @@ const docTemplate = `{ } } }, + "requests.UpdateComponentTypeRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string" + } + } + }, "requests.UpdateContainerRequest": { "type": "object", "properties": { @@ -1848,6 +2184,14 @@ const docTemplate = `{ } } }, + "responses.CreateComponentTypeResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, "responses.CreateContainerResponse": { "type": "object", "properties": { @@ -1897,6 +2241,20 @@ const docTemplate = `{ } } }, + "responses.UpdateComponentTypeResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "responses.UpdateContainerResponse": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index db05b79..fa9f000 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -9,6 +9,279 @@ "host": "localhost:3000", "basePath": "/api/v1", "paths": { + "/api/v1/component-types": { + "get": { + "description": "Retrieve a list of all component types ordered by creation date", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "List all component types", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/models.ComponentType" + } + } + } + } + ] + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + }, + "post": { + "description": "Create a new component type with the provided details", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Create a new component type", + "parameters": [ + { + "description": "Component type request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.CreateComponentTypeRequest" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.CreateComponentTypeResponse" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.ErrorResponse" + } + } + } + } + }, + "/api/v1/component-types/{id}": { + "get": { + "description": "Retrieve a single component type using its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Get component type by ID", + "parameters": [ + { + "type": "integer", + "description": "Component type ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.ComponentType" + } + } + } + ] + } + }, + "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 component type by its ID. Only non-empty fields will be updated.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Update component type", + "parameters": [ + { + "type": "integer", + "description": "Component type ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Component type request body", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UpdateComponentTypeRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.SuccessResponse" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/responses.UpdateComponentTypeResponse" + } + } + } + ] + } + }, + "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 component type by its unique identifier", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "component-type" + ], + "summary": "Delete component type", + "parameters": [ + { + "type": "integer", + "description": "Component type 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" + } + } + } + } + }, "/auth/register": { "post": { "description": "Register with email, username and password", @@ -1483,6 +1756,32 @@ } } }, + "models.ComponentType": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, "models.Container": { "type": "object", "properties": { @@ -1631,6 +1930,26 @@ } } }, + "requests.CreateComponentTypeRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string" + } + } + }, "requests.CreateContainerRequest": { "type": "object", "required": [ @@ -1731,6 +2050,23 @@ } } }, + "requests.UpdateComponentTypeRequest": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "metadata": { + "type": "array", + "items": { + "type": "integer" + } + }, + "name": { + "type": "string" + } + } + }, "requests.UpdateContainerRequest": { "type": "object", "properties": { @@ -1842,6 +2178,14 @@ } } }, + "responses.CreateComponentTypeResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, "responses.CreateContainerResponse": { "type": "object", "properties": { @@ -1891,6 +2235,20 @@ } } }, + "responses.UpdateComponentTypeResponse": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + } + }, "responses.UpdateContainerResponse": { "type": "object", "properties": { diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index 640c653..f98245b 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -15,6 +15,23 @@ definitions: updatedAt: type: string type: object + models.ComponentType: + properties: + createdAt: + type: string + description: + type: string + id: + type: integer + metadata: + items: + type: integer + type: array + name: + type: string + updatedAt: + type: string + type: object models.Container: properties: containerType: @@ -113,6 +130,19 @@ definitions: - name - roomId type: object + requests.CreateComponentTypeRequest: + properties: + description: + type: string + metadata: + items: + type: integer + type: array + name: + type: string + required: + - name + type: object requests.CreateContainerRequest: properties: containerType: @@ -180,6 +210,17 @@ definitions: name: type: string type: object + requests.UpdateComponentTypeRequest: + properties: + description: + type: string + metadata: + items: + type: integer + type: array + name: + type: string + type: object requests.UpdateContainerRequest: properties: containerType: @@ -252,6 +293,11 @@ definitions: id: type: integer type: object + responses.CreateComponentTypeResponse: + properties: + id: + type: integer + type: object responses.CreateContainerResponse: properties: id: @@ -283,6 +329,15 @@ definitions: roomId: type: integer type: object + responses.UpdateComponentTypeResponse: + properties: + description: + type: string + id: + type: integer + name: + type: string + type: object responses.UpdateContainerResponse: properties: containerType: @@ -344,6 +399,176 @@ info: title: Warehouse Management API version: "1.0" paths: + /api/v1/component-types: + get: + consumes: + - application/json + description: Retrieve a list of all component types ordered by creation date + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + items: + $ref: '#/definitions/models.ComponentType' + type: array + type: object + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.ErrorResponse' + summary: List all component types + tags: + - component-type + post: + consumes: + - application/json + description: Create a new component type with the provided details + parameters: + - description: Component type request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/requests.CreateComponentTypeRequest' + produces: + - application/json + responses: + "201": + description: Created + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/responses.CreateComponentTypeResponse' + 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 component type + tags: + - component-type + /api/v1/component-types/{id}: + delete: + consumes: + - application/json + description: Delete a component type by its unique identifier + parameters: + - description: Component type 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 component type + tags: + - component-type + get: + consumes: + - application/json + description: Retrieve a single component type using its unique identifier + parameters: + - description: Component type 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.ComponentType' + 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 component type by ID + tags: + - component-type + put: + consumes: + - application/json + description: Update an existing component type by its ID. Only non-empty fields + will be updated. + parameters: + - description: Component type ID + in: path + name: id + required: true + type: integer + - description: Component type request body + in: body + name: body + required: true + schema: + $ref: '#/definitions/requests.UpdateComponentTypeRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.SuccessResponse' + - properties: + data: + $ref: '#/definitions/responses.UpdateComponentTypeResponse' + 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 component type + tags: + - component-type /auth/register: post: consumes: diff --git a/internal/mapper/component_type_mapper.go b/internal/mapper/component_type_mapper.go new file mode 100644 index 0000000..62909a4 --- /dev/null +++ b/internal/mapper/component_type_mapper.go @@ -0,0 +1,50 @@ +package mapper + +import ( + "wm-backend/internal/models" + db "wm-backend/sqlc_gen" + + "encoding/json" + + "github.com/jackc/pgx/v5/pgtype" +) + +func ToDomainComponentType(r db.ComponentType) *models.ComponentType { + return &models.ComponentType{ + ID: r.ID, + Name: r.Name, + Description: r.Description.String, + Metadata: json.RawMessage(r.Metadata), + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + } +} + +func ToModelComponentType(r *models.ComponentType) *db.CreateComponentTypeParams { + return &db.CreateComponentTypeParams{ + Name: r.Name, + Description: pgtype.Text{ + String: r.Description, + Valid: r.Description != "", + }, + Metadata: []byte(r.Metadata), + CreatedAt: r.CreatedAt, + } +} + +func ToUpdateModelComponentType(r *models.ComponentType) *db.UpdateComponentTypeParams { + var metadata []byte + if len(r.Metadata) > 0 { + metadata = []byte(r.Metadata) + } + return &db.UpdateComponentTypeParams{ + Name: r.Name, + Description: pgtype.Text{ + String: r.Description, + Valid: r.Description != "", + }, + Metadata: metadata, + UpdatedAt: r.UpdatedAt, + ID: r.ID, + } +} diff --git a/internal/models/component_type_model.go b/internal/models/component_type_model.go new file mode 100644 index 0000000..e0e6117 --- /dev/null +++ b/internal/models/component_type_model.go @@ -0,0 +1,15 @@ +package models + +import ( + "encoding/json" + "time" +) + +type ComponentType struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Metadata json.RawMessage `json:"metadata"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/internal/models/requests/component_type_request.go b/internal/models/requests/component_type_request.go new file mode 100644 index 0000000..1024c08 --- /dev/null +++ b/internal/models/requests/component_type_request.go @@ -0,0 +1,15 @@ +package requests + +import "encoding/json" + +type CreateComponentTypeRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + Metadata json.RawMessage `json:"metadata"` +} + +type UpdateComponentTypeRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Metadata json.RawMessage `json:"metadata"` +} diff --git a/internal/models/responses/component_type_response.go b/internal/models/responses/component_type_response.go new file mode 100644 index 0000000..4b2d4f1 --- /dev/null +++ b/internal/models/responses/component_type_response.go @@ -0,0 +1,11 @@ +package responses + +type CreateComponentTypeResponse struct { + ID int64 `json:"id"` +} + +type UpdateComponentTypeResponse struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} diff --git a/internal/repositories/component_type_repository.go b/internal/repositories/component_type_repository.go new file mode 100644 index 0000000..b34dcde --- /dev/null +++ b/internal/repositories/component_type_repository.go @@ -0,0 +1,52 @@ +package repositories + +import ( + "context" + "wm-backend/internal/mapper" + "wm-backend/internal/models" + db "wm-backend/sqlc_gen" +) + +func CreateComponentType(ctx context.Context, queries *db.Queries, body models.ComponentType) (models.ComponentType, error) { + result, err := queries.CreateComponentType(ctx, *mapper.ToModelComponentType(&body)) + if err != nil { + return models.ComponentType{}, err + } + return *mapper.ToDomainComponentType(result), nil +} + +func GetComponentTypeByID(ctx context.Context, queries *db.Queries, id int64) (models.ComponentType, error) { + result, err := queries.GetComponentTypeByID(ctx, id) + if err != nil { + return models.ComponentType{}, err + } + return *mapper.ToDomainComponentType(result), nil +} + +func ListComponentTypes(ctx context.Context, queries *db.Queries) ([]models.ComponentType, error) { + results, err := queries.ListComponentTypes(ctx) + if err != nil { + return nil, err + } + var items []models.ComponentType + for _, r := range results { + items = append(items, *mapper.ToDomainComponentType(r)) + } + return items, nil +} + +func UpdateComponentType(ctx context.Context, queries *db.Queries, body models.ComponentType) (models.ComponentType, error) { + result, err := queries.UpdateComponentType(ctx, *mapper.ToUpdateModelComponentType(&body)) + if err != nil { + return models.ComponentType{}, err + } + return *mapper.ToDomainComponentType(result), nil +} + +func DeleteComponentType(ctx context.Context, queries *db.Queries, id int64) (int64, error) { + rowsAffected, err := queries.DeleteComponentType(ctx, id) + if err != nil { + return rowsAffected, err + } + return rowsAffected, nil +} diff --git a/internal/routers/router.go b/internal/routers/router.go index d2d91e6..1b9cad4 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -73,6 +73,15 @@ func NewRouter() *gin.Engine { container.PUT("/:id", utils.AsyncHandler(services.ContainerUpdate)) container.DELETE("/:id", utils.AsyncHandler(services.ContainerDelete)) } + + componentType := v1.Group(constants.API_GROUP_COMPONENT_TYPE) + { + componentType.GET("", utils.AsyncHandler(services.ComponentTypeList)) + componentType.GET("/:id", utils.AsyncHandler(services.ComponentTypeGetByID)) + componentType.POST("", utils.AsyncHandler(services.ComponentTypeCreate)) + componentType.PUT("/:id", utils.AsyncHandler(services.ComponentTypeUpdate)) + componentType.DELETE("/:id", utils.AsyncHandler(services.ComponentTypeDelete)) + } } r.GET(constants.API_PATH_PING, services.PingHandler) diff --git a/internal/services/component_type_service.go b/internal/services/component_type_service.go new file mode 100644 index 0000000..1f066f9 --- /dev/null +++ b/internal/services/component_type_service.go @@ -0,0 +1,186 @@ +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" +) + +// ComponentTypeCreate creates a new component type. +// It validates the request body and creates the component type in the database. +// +// @Summary Create a new component type +// @Description Create a new component type with the provided details +// @Tags component-type +// @Accept json +// @Produce json +// @Param body body requests.CreateComponentTypeRequest true "Component type request body" +// @Success 201 {object} response.SuccessResponse{data=responses.CreateComponentTypeResponse} +// @Failure 400 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /api/v1/component-types [post] +func ComponentTypeCreate(c *gin.Context) error { + requestBody := requests.CreateComponentTypeRequest{} + if helper.IsShouldBindJSON(c, &requestBody) { + return nil + } + componentTypeModel := &models.ComponentType{ + Name: requestBody.Name, + Description: requestBody.Description, + Metadata: requestBody.Metadata, + CreatedAt: time.Now(), + } + componentType, err := repositories.CreateComponentType(c.Request.Context(), global.Queries, *componentTypeModel) + if err != nil { + response.InternalServerError(c, http.StatusInternalServerError, "Failed to create component type") + return nil + } + response.Created(c, "Component type created successfully", &responses.CreateComponentTypeResponse{ + ID: componentType.ID, + }) + return nil +} + +// ComponentTypeGetByID retrieves a single component type by its ID. +// +// @Summary Get component type by ID +// @Description Retrieve a single component type using its unique identifier +// @Tags component-type +// @Accept json +// @Produce json +// @Param id path int true "Component type ID" +// @Success 200 {object} response.SuccessResponse{data=models.ComponentType} +// @Failure 400 {object} response.ErrorResponse +// @Failure 404 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /api/v1/component-types/{id} [get] +func ComponentTypeGetByID(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 + } + componentType, err := repositories.GetComponentTypeByID(c.Request.Context(), global.Queries, id) + if err != nil { + response.NotFoundError(c, http.StatusNotFound, "Component type not found") + return nil + } + response.Ok(c, "Success", componentType) + return nil +} + +// ComponentTypeList retrieves all component types. +// +// @Summary List all component types +// @Description Retrieve a list of all component types ordered by creation date +// @Tags component-type +// @Accept json +// @Produce json +// @Success 200 {object} response.SuccessResponse{data=[]models.ComponentType} +// @Failure 500 {object} response.ErrorResponse +// @Router /api/v1/component-types [get] +func ComponentTypeList(c *gin.Context) error { + componentTypes, err := repositories.ListComponentTypes(c.Request.Context(), global.Queries) + if err != nil { + response.InternalServerError(c, http.StatusInternalServerError, "Failed to list component types") + return nil + } + response.Ok(c, "Success", componentTypes) + return nil +} + +// ComponentTypeUpdate updates an existing component type by its ID. +// It validates the request body, fetches the existing record, +// merges non-empty fields from the request, and updates the component type in the database. +// +// @Summary Update component type +// @Description Update an existing component type by its ID. Only non-empty fields will be updated. +// @Tags component-type +// @Accept json +// @Produce json +// @Param id path int true "Component type ID" +// @Param body body requests.UpdateComponentTypeRequest true "Component type request body" +// @Success 200 {object} response.SuccessResponse{data=responses.UpdateComponentTypeResponse} +// @Failure 400 {object} response.ErrorResponse +// @Failure 404 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /api/v1/component-types/{id} [put] +func ComponentTypeUpdate(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.UpdateComponentTypeRequest{} + if helper.IsShouldBindJSON(c, &requestBody) { + return nil + } + existing, err := repositories.GetComponentTypeByID(c.Request.Context(), global.Queries, id) + if err != nil { + response.NotFoundError(c, http.StatusNotFound, "Component type not found") + return nil + } + if requestBody.Name != "" { + existing.Name = requestBody.Name + } + if requestBody.Description != "" { + existing.Description = requestBody.Description + } + if len(requestBody.Metadata) > 0 { + existing.Metadata = requestBody.Metadata + } + existing.UpdatedAt = time.Now() + componentType, err := repositories.UpdateComponentType(c.Request.Context(), global.Queries, existing) + if err != nil { + response.InternalServerError(c, http.StatusInternalServerError, "Failed to update component type") + return nil + } + response.Ok(c, "Component type updated successfully", &responses.UpdateComponentTypeResponse{ + ID: componentType.ID, + Name: componentType.Name, + Description: componentType.Description, + }) + return nil +} + +// ComponentTypeDelete deletes a component type by its ID. +// +// @Summary Delete component type +// @Description Delete a component type by its unique identifier +// @Tags component-type +// @Accept json +// @Produce json +// @Param id path int true "Component type ID" +// @Success 200 {object} response.SuccessResponse +// @Failure 400 {object} response.ErrorResponse +// @Failure 500 {object} response.ErrorResponse +// @Router /api/v1/component-types/{id} [delete] +func ComponentTypeDelete(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.DeleteComponentType(c.Request.Context(), global.Queries, id) + if err != nil { + log.Error().Err(err).Msgf("Failed to delete component type with ID: %d", id) + response.InternalServerError(c, http.StatusInternalServerError, "Failed to delete component type") + return nil + } + if rowsAffected == 0 { + response.NotFoundError(c, http.StatusNotFound, "Component type not found") + return nil + } + response.Ok(c, "Delete Success", nil) + return nil +} diff --git a/sqlc_gen/conponent_type.sql.go b/sqlc_gen/conponent_type.sql.go new file mode 100644 index 0000000..090b43f --- /dev/null +++ b/sqlc_gen/conponent_type.sql.go @@ -0,0 +1,152 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: conponent_type.sql + +package db + +import ( + "context" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createComponentType = `-- name: CreateComponentType :one +INSERT INTO component_types (name, description,metadata, created_at) +VALUES ( + $1, + $2, + $3, + $4 +) +RETURNING id, name, description, metadata, created_at, updated_at +` + +type CreateComponentTypeParams struct { + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Metadata []byte `db:"metadata" json:"metadata"` + CreatedAt time.Time `db:"created_at" json:"createdAt"` +} + +func (q *Queries) CreateComponentType(ctx context.Context, arg CreateComponentTypeParams) (ComponentType, error) { + row := q.db.QueryRow(ctx, createComponentType, + arg.Name, + arg.Description, + arg.Metadata, + arg.CreatedAt, + ) + var i ComponentType + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.Metadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const deleteComponentType = `-- name: DeleteComponentType :execrows +DELETE FROM component_types +WHERE id = $1 +` + +func (q *Queries) DeleteComponentType(ctx context.Context, id int64) (int64, error) { + result, err := q.db.Exec(ctx, deleteComponentType, id) + if err != nil { + return 0, err + } + return result.RowsAffected(), nil +} + +const getComponentTypeByID = `-- name: GetComponentTypeByID :one +SELECT id, name, description, metadata, created_at, updated_at FROM component_types +WHERE id = $1 +` + +func (q *Queries) GetComponentTypeByID(ctx context.Context, id int64) (ComponentType, error) { + row := q.db.QueryRow(ctx, getComponentTypeByID, id) + var i ComponentType + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.Metadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const listComponentTypes = `-- name: ListComponentTypes :many +SELECT id, name, description, metadata, created_at, updated_at FROM component_types +ORDER BY created_at DESC +` + +func (q *Queries) ListComponentTypes(ctx context.Context) ([]ComponentType, error) { + rows, err := q.db.Query(ctx, listComponentTypes) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ComponentType + for rows.Next() { + var i ComponentType + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Description, + &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 updateComponentType = `-- name: UpdateComponentType :one +UPDATE component_types +SET name = CASE WHEN $1 = '' THEN name ELSE $1 END, + description = coalesce($2, description), + metadata = coalesce($3, metadata), + updated_at = $4 +WHERE id = $5 +RETURNING id, name, description, metadata, created_at, updated_at +` + +type UpdateComponentTypeParams struct { + Name interface{} `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Metadata []byte `db:"metadata" json:"metadata"` + UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` + ID int64 `db:"id" json:"id"` +} + +func (q *Queries) UpdateComponentType(ctx context.Context, arg UpdateComponentTypeParams) (ComponentType, error) { + row := q.db.QueryRow(ctx, updateComponentType, + arg.Name, + arg.Description, + arg.Metadata, + arg.UpdatedAt, + arg.ID, + ) + var i ComponentType + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.Metadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/sqlc_gen/querier.go b/sqlc_gen/querier.go index 71e3938..6f4d170 100644 --- a/sqlc_gen/querier.go +++ b/sqlc_gen/querier.go @@ -14,6 +14,7 @@ type Querier interface { AssignRoleToUser(ctx context.Context, arg AssignRoleToUserParams) (UserRole, error) CountUsersByRoleID(ctx context.Context, roleID uuid.UUID) (int64, error) CreateCabinet(ctx context.Context, arg CreateCabinetParams) (Cabinet, error) + CreateComponentType(ctx context.Context, arg CreateComponentTypeParams) (ComponentType, error) CreateContainer(ctx context.Context, arg CreateContainerParams) (Container, error) CreateRole(ctx context.Context, arg CreateRoleParams) (Role, error) CreateRoom(ctx context.Context, arg CreateRoomParams) (Room, error) @@ -21,12 +22,14 @@ type Querier interface { CreateUser(ctx context.Context, arg CreateUserParams) (uuid.UUID, error) CreateWarehouse(ctx context.Context, arg CreateWarehouseParams) (Warehouse, error) DeleteCabinet(ctx context.Context, id int64) (int64, error) + DeleteComponentType(ctx context.Context, id int64) (int64, error) DeleteContainer(ctx context.Context, id int64) (int64, error) DeleteRole(ctx context.Context, id uuid.UUID) (int64, error) DeleteRoom(ctx context.Context, id int64) (int64, error) DeleteShelve(ctx context.Context, id int64) (int64, error) DeleteWarehouse(ctx context.Context, id int64) (int64, error) GetCabinetByID(ctx context.Context, id int64) (Cabinet, error) + GetComponentTypeByID(ctx context.Context, id int64) (ComponentType, error) GetContainerByID(ctx context.Context, id int64) (Container, error) GetRoleByID(ctx context.Context, id uuid.UUID) (Role, error) GetRoomByID(ctx context.Context, id int64) (Room, error) @@ -39,6 +42,7 @@ type Querier interface { GetUserRolesByUserID(ctx context.Context, userID uuid.UUID) ([]GetUserRolesByUserIDRow, error) GetWarehouseByID(ctx context.Context, id int64) (Warehouse, error) ListCabinets(ctx context.Context) ([]Cabinet, error) + ListComponentTypes(ctx context.Context) ([]ComponentType, error) ListContainers(ctx context.Context) ([]Container, error) ListRoles(ctx context.Context) ([]Role, error) ListRooms(ctx context.Context) ([]Room, error) @@ -47,6 +51,7 @@ type Querier interface { RemoveAllRolesFromUser(ctx context.Context, userID uuid.UUID) error RemoveRoleFromUser(ctx context.Context, arg RemoveRoleFromUserParams) error UpdateCabinet(ctx context.Context, arg UpdateCabinetParams) (Cabinet, error) + UpdateComponentType(ctx context.Context, arg UpdateComponentTypeParams) (ComponentType, error) UpdateContainer(ctx context.Context, arg UpdateContainerParams) (Container, error) UpdateRole(ctx context.Context, arg UpdateRoleParams) (Role, error) UpdateRoom(ctx context.Context, arg UpdateRoomParams) (Room, error)