Implemented families
- Added families. - Added test for all endpoints - Updated users DTO
This commit is contained in:
+310
-56
@@ -15,6 +15,240 @@ const docTemplate = `{
|
|||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/families": {
|
||||||
|
"post": {
|
||||||
|
"description": "Создает новую семью",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Создать семью",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Family info",
|
||||||
|
"name": "family",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.CreateFamilyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.FamilyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid body",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/families/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Возвращает семью по ее внутреннему ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Получить семью по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Family ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.FamilyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "family not found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Удаляет семью по ее ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Удалить семью",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Family ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "no content",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "family not found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"description": "Частично обновляет данные семьи по ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Обновить семью",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Family ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Данные для обновления",
|
||||||
|
"name": "family",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UpdateFamilyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.FamilyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "name is required",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "family not found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/users": {
|
"/users": {
|
||||||
"post": {
|
"post": {
|
||||||
"consumes": [
|
"consumes": [
|
||||||
@@ -48,19 +282,13 @@ const docTemplate = `{
|
|||||||
"400": {
|
"400": {
|
||||||
"description": "Bad Request",
|
"description": "Bad Request",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "Internal Server Error",
|
"description": "Internal Server Error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,28 +326,19 @@ const docTemplate = `{
|
|||||||
"400": {
|
"400": {
|
||||||
"description": "invalid telegram id",
|
"description": "invalid telegram id",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "user not found",
|
"description": "user not found",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error",
|
"description": "internal server error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,28 +376,19 @@ const docTemplate = `{
|
|||||||
"400": {
|
"400": {
|
||||||
"description": "invalid id",
|
"description": "invalid id",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "user not found",
|
"description": "user not found",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error",
|
"description": "internal server error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,28 +424,19 @@ const docTemplate = `{
|
|||||||
"400": {
|
"400": {
|
||||||
"description": "invalid id",
|
"description": "invalid id",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "user not found",
|
"description": "user not found",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error",
|
"description": "internal server error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,28 +481,19 @@ const docTemplate = `{
|
|||||||
"400": {
|
"400": {
|
||||||
"description": "invalid id or invalid body",
|
"description": "invalid id or invalid body",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "user not found",
|
"description": "user not found",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"500": {
|
"500": {
|
||||||
"description": "internal server error",
|
"description": "internal server error",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,6 +501,23 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
"dto.CreateFamilyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.CreateUserRequest": {
|
"dto.CreateUserRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -333,6 +542,43 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.FamilyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.UpdateFamilyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"telegram_chat_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.UpdateUserRequest": {
|
"dto.UpdateUserRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -350,6 +596,14 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.UserErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.UserResponse": {
|
"dto.UserResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -0,0 +1,626 @@
|
|||||||
|
{
|
||||||
|
"swagger": "2.0",
|
||||||
|
"info": {
|
||||||
|
"contact": {}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/families": {
|
||||||
|
"post": {
|
||||||
|
"description": "Создает новую семью",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Создать семью",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Family info",
|
||||||
|
"name": "family",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.CreateFamilyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.FamilyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid body",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/families/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Возвращает семью по ее внутреннему ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Получить семью по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Family ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.FamilyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "family not found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Удаляет семью по ее ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Удалить семью",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Family ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "no content",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "family not found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"description": "Частично обновляет данные семьи по ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Families"
|
||||||
|
],
|
||||||
|
"summary": "Обновить семью",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Family ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Данные для обновления",
|
||||||
|
"name": "family",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UpdateFamilyRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.FamilyResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "name is required",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "family not found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users": {
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Создать пользователя",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "User info",
|
||||||
|
"name": "user",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.CreateUserRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/by-telegram/{telegramId}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Возвращает пользователя по его Telegram ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Получить пользователя по Telegram ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Telegram ID",
|
||||||
|
"name": "telegramId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid telegram id",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "user not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Возвращает пользователя по его внутреннему ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Получить пользователя по ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "user not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"description": "Удаляет пользователя по его ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Удалить пользователя",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "no content",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "user not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"description": "Частично обновляет данные пользователя по ID",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Обновить пользователя",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "User ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Данные для обновления",
|
||||||
|
"name": "user",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UpdateUserRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "invalid id or invalid body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "user not found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.UserErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"definitions": {
|
||||||
|
"dto.CreateFamilyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.CreateUserRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"first_name",
|
||||||
|
"telegram_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"language_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"telegram_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.FamilyResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"owner_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"telegram_chat_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.UpdateFamilyRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"telegram_chat_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.UpdateUserRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"language_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.UserErrorResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.UserResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"language_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"telegram_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,411 @@
|
|||||||
|
definitions:
|
||||||
|
dto.CreateFamilyRequest:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner_id:
|
||||||
|
type: integer
|
||||||
|
telegram_chat_id:
|
||||||
|
type: integer
|
||||||
|
telegram_chat_name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.CreateUserRequest:
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
language_code:
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
telegram_id:
|
||||||
|
type: integer
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- first_name
|
||||||
|
- telegram_id
|
||||||
|
type: object
|
||||||
|
dto.FamilyResponse:
|
||||||
|
properties:
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
owner_id:
|
||||||
|
type: integer
|
||||||
|
telegram_chat_id:
|
||||||
|
type: integer
|
||||||
|
telegram_chat_name:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.UpdateFamilyRequest:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
telegram_chat_name:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.UpdateUserRequest:
|
||||||
|
properties:
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
language_code:
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.UserErrorResponse:
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
dto.UserResponse:
|
||||||
|
properties:
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
language_code:
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
telegram_id:
|
||||||
|
type: integer
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
info:
|
||||||
|
contact: {}
|
||||||
|
paths:
|
||||||
|
/families:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Создает новую семью
|
||||||
|
parameters:
|
||||||
|
- description: Family info
|
||||||
|
in: body
|
||||||
|
name: family
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.CreateFamilyRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.FamilyResponse'
|
||||||
|
"400":
|
||||||
|
description: invalid body
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Создать семью
|
||||||
|
tags:
|
||||||
|
- Families
|
||||||
|
/families/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Удаляет семью по ее ID
|
||||||
|
parameters:
|
||||||
|
- description: Family ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: no content
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: invalid id
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: family not found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Удалить семью
|
||||||
|
tags:
|
||||||
|
- Families
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Возвращает семью по ее внутреннему ID
|
||||||
|
parameters:
|
||||||
|
- description: Family ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.FamilyResponse'
|
||||||
|
"400":
|
||||||
|
description: invalid id
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: family not found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Получить семью по ID
|
||||||
|
tags:
|
||||||
|
- Families
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Частично обновляет данные семьи по ID
|
||||||
|
parameters:
|
||||||
|
- description: Family ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Данные для обновления
|
||||||
|
in: body
|
||||||
|
name: family
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UpdateFamilyRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.FamilyResponse'
|
||||||
|
"400":
|
||||||
|
description: name is required
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: family not found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Обновить семью
|
||||||
|
tags:
|
||||||
|
- Families
|
||||||
|
/users:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- description: User info
|
||||||
|
in: body
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.CreateUserRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
summary: Создать пользователя
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
/users/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Удаляет пользователя по его ID
|
||||||
|
parameters:
|
||||||
|
- description: User ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: no content
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"400":
|
||||||
|
description: invalid id
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: user not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
summary: Удалить пользователя
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Возвращает пользователя по его внутреннему ID
|
||||||
|
parameters:
|
||||||
|
- description: User ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserResponse'
|
||||||
|
"400":
|
||||||
|
description: invalid id
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: user not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
summary: Получить пользователя по ID
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
patch:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Частично обновляет данные пользователя по ID
|
||||||
|
parameters:
|
||||||
|
- description: User ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Данные для обновления
|
||||||
|
in: body
|
||||||
|
name: user
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UpdateUserRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserResponse'
|
||||||
|
"400":
|
||||||
|
description: invalid id or invalid body
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: user not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
summary: Обновить пользователя
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
/users/by-telegram/{telegramId}:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Возвращает пользователя по его Telegram ID
|
||||||
|
parameters:
|
||||||
|
- description: Telegram ID
|
||||||
|
in: path
|
||||||
|
name: telegramId
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserResponse'
|
||||||
|
"400":
|
||||||
|
description: invalid telegram id
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"404":
|
||||||
|
description: user not found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
"500":
|
||||||
|
description: internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.UserErrorResponse'
|
||||||
|
summary: Получить пользователя по Telegram ID
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
swagger: "2.0"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/domain"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateFamilyRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
OwnerID int64 `json:"owner_id"`
|
||||||
|
TelegramChatID int64 `json:"telegram_chat_id"`
|
||||||
|
TelegramChatName string `json:"telegram_chat_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateFamilyRequest struct {
|
||||||
|
Name *string `json:"name"`
|
||||||
|
TelegramChatName string `json:"telegram_chat_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FamilyResponse struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
OwnerID int64 `json:"owner_id"`
|
||||||
|
TelegramChatID int64 `json:"telegram_chat_id"`
|
||||||
|
TelegramChatName string `json:"telegram_chat_name"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response *FamilyResponse) ModelToResponse(f *domain.Family) FamilyResponse {
|
||||||
|
return FamilyResponse{
|
||||||
|
ID: f.ID,
|
||||||
|
Name: f.Name,
|
||||||
|
OwnerID: f.OwnerID,
|
||||||
|
TelegramChatID: f.TelegramChatID,
|
||||||
|
TelegramChatName: f.TelegramChatName,
|
||||||
|
CreatedAt: f.CreatedAt.Format(time.RFC3339),
|
||||||
|
UpdatedAt: f.UpdatedAt.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,10 @@ type UserResponse struct {
|
|||||||
UpdatedAt string `json:"updated_at"`
|
UpdatedAt string `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
func (response *UserResponse) ModelToResponse(u *domain.User) UserResponse {
|
func (response *UserResponse) ModelToResponse(u *domain.User) UserResponse {
|
||||||
return UserResponse{
|
return UserResponse{
|
||||||
ID: u.ID,
|
ID: u.ID,
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/api/dto"
|
||||||
|
"FamilyHub/src/api/services"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FamiliesRouter struct {
|
||||||
|
service services.FamilyService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFamiliesRouter(s services.FamilyService) *FamiliesRouter {
|
||||||
|
return &FamiliesRouter{service: s}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (router *FamiliesRouter) RegisterRoutes(r *gin.RouterGroup) {
|
||||||
|
families := r.Group("/families")
|
||||||
|
{
|
||||||
|
families.POST("", router.Create)
|
||||||
|
families.GET("/:id", router.GetByID)
|
||||||
|
families.PATCH("/:id", router.Update)
|
||||||
|
families.DELETE("/:id", router.Delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GoDoc
|
||||||
|
// @Summary Создать семью
|
||||||
|
// @Description Создает новую семью
|
||||||
|
// @Tags Families
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param family body dto.CreateFamilyRequest true "Family info"
|
||||||
|
// @Success 201 {object} dto.FamilyResponse
|
||||||
|
// @Failure 400 {object} map[string]string "invalid body"
|
||||||
|
// @Failure 500 {object} map[string]string "internal server error"
|
||||||
|
// @Router /families [post]
|
||||||
|
func (router *FamiliesRouter) Create(c *gin.Context) {
|
||||||
|
var req dto.CreateFamilyRequest
|
||||||
|
var resp dto.FamilyResponse
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
family, err := router.service.Create(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
handleFamilyError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, resp.ModelToResponse(family))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetByID GoDoc
|
||||||
|
// @Summary Получить семью по ID
|
||||||
|
// @Description Возвращает семью по ее внутреннему ID
|
||||||
|
// @Tags Families
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Family ID"
|
||||||
|
// @Success 200 {object} dto.FamilyResponse
|
||||||
|
// @Failure 400 {object} map[string]string "invalid id"
|
||||||
|
// @Failure 404 {object} map[string]string "family not found"
|
||||||
|
// @Failure 500 {object} map[string]string "internal server error"
|
||||||
|
// @Router /families/{id} [get]
|
||||||
|
func (router *FamiliesRouter) GetByID(c *gin.Context) {
|
||||||
|
var resp dto.FamilyResponse
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
family, err := router.service.GetByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
handleFamilyError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp.ModelToResponse(family))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update GoDoc
|
||||||
|
// @Summary Обновить семью
|
||||||
|
// @Description Частично обновляет данные семьи по ID
|
||||||
|
// @Tags Families
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Family ID"
|
||||||
|
// @Param family body dto.UpdateFamilyRequest true "Данные для обновления"
|
||||||
|
// @Success 200 {object} dto.FamilyResponse
|
||||||
|
// @Failure 400 {object} map[string]string "invalid id or invalid body"
|
||||||
|
// @Failure 400 {object} map[string]string "name is required"
|
||||||
|
// @Failure 404 {object} map[string]string "family not found"
|
||||||
|
// @Failure 500 {object} map[string]string "internal server error"
|
||||||
|
// @Router /families/{id} [patch]
|
||||||
|
func (router *FamiliesRouter) Update(c *gin.Context) {
|
||||||
|
var resp dto.FamilyResponse
|
||||||
|
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req dto.UpdateFamilyRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.Name == nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "name is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
family, err := router.service.Update(c.Request.Context(), id, req)
|
||||||
|
if err != nil {
|
||||||
|
handleFamilyError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp.ModelToResponse(family))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete GoDoc
|
||||||
|
// @Summary Удалить семью
|
||||||
|
// @Description Удаляет семью по ее ID
|
||||||
|
// @Tags Families
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "Family ID"
|
||||||
|
// @Success 204 {string} string "no content"
|
||||||
|
// @Failure 400 {object} map[string]string "invalid id"
|
||||||
|
// @Failure 404 {object} map[string]string "family not found"
|
||||||
|
// @Failure 500 {object} map[string]string "internal server error"
|
||||||
|
// @Router /families/{id} [delete]
|
||||||
|
func (router *FamiliesRouter) Delete(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := router.service.Delete(c.Request.Context(), id); err != nil {
|
||||||
|
handleFamilyError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFamilyError(c *gin.Context, err error) {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, services.ErrFamilyNotFound):
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||||
|
case errors.Is(err, sql.ErrNoRows):
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "family not found"})
|
||||||
|
default:
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/api/dto"
|
||||||
|
"FamilyHub/src/api/services"
|
||||||
|
"FamilyHub/src/domain"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type familyServiceMock struct {
|
||||||
|
createFn func(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error)
|
||||||
|
getByIDFn func(ctx context.Context, id int64) (*domain.Family, error)
|
||||||
|
updateFn func(ctx context.Context, id int64, req dto.UpdateFamilyRequest) (*domain.Family, error)
|
||||||
|
deleteFn func(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *familyServiceMock) Create(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error) {
|
||||||
|
if m.createFn != nil {
|
||||||
|
return m.createFn(ctx, req)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock create is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *familyServiceMock) GetByID(ctx context.Context, id int64) (*domain.Family, error) {
|
||||||
|
if m.getByIDFn != nil {
|
||||||
|
return m.getByIDFn(ctx, id)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock getByID is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *familyServiceMock) Update(ctx context.Context, id int64, req dto.UpdateFamilyRequest) (*domain.Family, error) {
|
||||||
|
if m.updateFn != nil {
|
||||||
|
return m.updateFn(ctx, id, req)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock update is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *familyServiceMock) Delete(ctx context.Context, id int64) error {
|
||||||
|
if m.deleteFn != nil {
|
||||||
|
return m.deleteFn(ctx, id)
|
||||||
|
}
|
||||||
|
return errors.New("mock delete is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupFamiliesRouter(mock services.FamilyService) *gin.Engine {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
r := gin.New()
|
||||||
|
apiV1 := r.Group("/api/v1")
|
||||||
|
router := NewFamiliesRouter(mock)
|
||||||
|
router.RegisterRoutes(apiV1)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleFamily() *domain.Family {
|
||||||
|
return &domain.Family{
|
||||||
|
ID: 7,
|
||||||
|
Name: "Belan",
|
||||||
|
OwnerID: 10,
|
||||||
|
TelegramChatID: 12345,
|
||||||
|
TelegramChatName: "Family Chat",
|
||||||
|
CreatedAt: time.Date(2026, time.January, 21, 10, 0, 0, 0, time.UTC),
|
||||||
|
UpdatedAt: time.Date(2026, time.January, 21, 11, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFamiliesRouter_Create(t *testing.T) {
|
||||||
|
t.Run("bad request on malformed body", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/families", bytes.NewBufferString(`{"name":`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("internal error", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{createFn: func(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error) {
|
||||||
|
return nil, errors.New("db unavailable")
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/families", bytes.NewBufferString(`{"name":"Belan","owner_id":10,"telegram_chat_id":12345,"telegram_chat_name":"Family Chat"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "internal server error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("created", func(t *testing.T) {
|
||||||
|
expected := sampleFamily()
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{createFn: func(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error) {
|
||||||
|
assert.Equal(t, "Belan", req.Name)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/families", bytes.NewBufferString(`{"name":"Belan","owner_id":10,"telegram_chat_id":12345,"telegram_chat_name":"Family Chat"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "Belan")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFamiliesRouter_GetByID(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/families/abc", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not found on service error", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.Family, error) {
|
||||||
|
return nil, services.ErrFamilyNotFound
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/families/7", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), services.ErrFamilyNotFound.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not found on sql no rows", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.Family, error) {
|
||||||
|
return nil, sql.ErrNoRows
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/families/7", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "family not found")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ok", func(t *testing.T) {
|
||||||
|
expected := sampleFamily()
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.Family, error) {
|
||||||
|
assert.Equal(t, int64(7), id)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/families/7", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "Belan")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFamiliesRouter_Update(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/families/abc", bytes.NewBufferString(`{"name":"Belan"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad request on malformed body", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/families/7", bytes.NewBufferString(`{"name":`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad request on missing name", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/families/7", bytes.NewBufferString(`{"telegram_chat_name":"Updated"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "name is required")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not found", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{updateFn: func(ctx context.Context, id int64, req dto.UpdateFamilyRequest) (*domain.Family, error) {
|
||||||
|
return nil, services.ErrFamilyNotFound
|
||||||
|
}})
|
||||||
|
name := "Belan Updated"
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/families/7", bytes.NewBufferString(`{"name":"`+name+`","telegram_chat_name":"Updated"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), services.ErrFamilyNotFound.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ok", func(t *testing.T) {
|
||||||
|
expected := sampleFamily()
|
||||||
|
updatedName := "Belan Updated"
|
||||||
|
expected.Name = updatedName
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{updateFn: func(ctx context.Context, id int64, req dto.UpdateFamilyRequest) (*domain.Family, error) {
|
||||||
|
assert.Equal(t, int64(7), id)
|
||||||
|
require.NotNil(t, req.Name)
|
||||||
|
assert.Equal(t, updatedName, *req.Name)
|
||||||
|
assert.Equal(t, "Updated", req.TelegramChatName)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/families/7", bytes.NewBufferString(`{"name":"`+updatedName+`","telegram_chat_name":"Updated"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), updatedName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFamiliesRouter_Delete(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/families/abc", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not found", func(t *testing.T) {
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{deleteFn: func(ctx context.Context, id int64) error {
|
||||||
|
return sql.ErrNoRows
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/families/7", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "family not found")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no content", func(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{deleteFn: func(ctx context.Context, id int64) error {
|
||||||
|
called = true
|
||||||
|
assert.Equal(t, int64(7), id)
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/families/7", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNoContent, w.Code)
|
||||||
|
assert.True(t, called)
|
||||||
|
assert.Empty(t, strings.TrimSpace(w.Body.String()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFamiliesRouter_Create_ResponseShape(t *testing.T) {
|
||||||
|
expected := sampleFamily()
|
||||||
|
r := setupFamiliesRouter(&familyServiceMock{createFn: func(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error) {
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/families", bytes.NewBufferString(`{"name":"Belan","owner_id":10,"telegram_chat_id":12345,"telegram_chat_name":"Family Chat"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
var resp dto.FamilyResponse
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expected.ID, resp.ID)
|
||||||
|
assert.Equal(t, expected.Name, resp.Name)
|
||||||
|
assert.Equal(t, expected.OwnerID, resp.OwnerID)
|
||||||
|
assert.Equal(t, expected.TelegramChatID, resp.TelegramChatID)
|
||||||
|
assert.Equal(t, expected.TelegramChatName, resp.TelegramChatName)
|
||||||
|
assert.Equal(t, expected.CreatedAt.Format(time.RFC3339), resp.CreatedAt)
|
||||||
|
assert.Equal(t, expected.UpdatedAt.Format(time.RFC3339), resp.UpdatedAt)
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"FamilyHub/src/api/dto"
|
"FamilyHub/src/api/dto"
|
||||||
"FamilyHub/src/integrations/receiptService"
|
"FamilyHub/src/domain"
|
||||||
"FamilyHub/src/utils"
|
"FamilyHub/src/utils"
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
@@ -12,11 +12,15 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReceiptRouter struct {
|
type receiptService interface {
|
||||||
service *receiptService.ReceiptService
|
GetReceipt(ctx context.Context, date string, number string) (*domain.Receipt, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReceiptRouter(s *receiptService.ReceiptService) *ReceiptRouter {
|
type ReceiptRouter struct {
|
||||||
|
service receiptService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReceiptRouter(s receiptService) *ReceiptRouter {
|
||||||
return &ReceiptRouter{service: s}
|
return &ReceiptRouter{service: s}
|
||||||
}
|
}
|
||||||
func (router *ReceiptRouter) RegisterRoutes(r *gin.RouterGroup) {
|
func (router *ReceiptRouter) RegisterRoutes(r *gin.RouterGroup) {
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/domain"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type receiptServiceMock struct {
|
||||||
|
getReceiptFn func(ctx context.Context, date string, number string) (*domain.Receipt, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *receiptServiceMock) GetReceipt(ctx context.Context, date string, number string) (*domain.Receipt, error) {
|
||||||
|
if m.getReceiptFn != nil {
|
||||||
|
return m.getReceiptFn(ctx, date, number)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReceiptRouter_AddReceipt(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
validNumber := strings.Repeat("1", 24)
|
||||||
|
validDate := "21.01.2026"
|
||||||
|
expectedDate := "2026-01-21"
|
||||||
|
now := time.Date(2026, time.January, 21, 10, 11, 12, 0, time.UTC)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
body string
|
||||||
|
mock *receiptServiceMock
|
||||||
|
expectedStatus int
|
||||||
|
expectedContains string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bad request on invalid body",
|
||||||
|
body: `{"date":"21.01.2026"}`,
|
||||||
|
mock: &receiptServiceMock{},
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedContains: "Number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad request on invalid date format",
|
||||||
|
body: `{"number":"` + validNumber + `","date":"2026-01-21"}`,
|
||||||
|
mock: &receiptServiceMock{},
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedContains: "invalid date format",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad request on service error",
|
||||||
|
body: `{"number":"` + validNumber + `","date":"` + validDate + `"}`,
|
||||||
|
mock: &receiptServiceMock{getReceiptFn: func(ctx context.Context, date string, number string) (*domain.Receipt, error) {
|
||||||
|
assert.Equal(t, expectedDate, date)
|
||||||
|
assert.Equal(t, validNumber, number)
|
||||||
|
return nil, errors.New("receipt not found")
|
||||||
|
}},
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedContains: "receipt not found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok",
|
||||||
|
body: `{"number":"` + validNumber + `","date":"` + validDate + `"}`,
|
||||||
|
mock: &receiptServiceMock{getReceiptFn: func(ctx context.Context, date string, number string) (*domain.Receipt, error) {
|
||||||
|
assert.Equal(t, expectedDate, date)
|
||||||
|
assert.Equal(t, validNumber, number)
|
||||||
|
return &domain.Receipt{ReceiptNumber: validNumber, IssuedAt: now}, nil
|
||||||
|
}},
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedContains: validNumber,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r := gin.New()
|
||||||
|
apiV1 := r.Group("/api/v1")
|
||||||
|
router := NewReceiptRouter(tc.mock)
|
||||||
|
router.RegisterRoutes(apiV1)
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/receipts", bytes.NewBufferString(tc.body))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, tc.expectedStatus, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), tc.expectedContains)
|
||||||
|
|
||||||
|
if tc.expectedStatus == http.StatusOK {
|
||||||
|
var resp struct {
|
||||||
|
ID int32 `json:"id"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, int32(1), resp.ID)
|
||||||
|
assert.Equal(t, validNumber, resp.Number)
|
||||||
|
assert.Equal(t, now, resp.Date)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-26
@@ -14,8 +14,8 @@ type UsersRouter struct {
|
|||||||
service services.UserService
|
service services.UserService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUsersRouter(s *services.UserService) *UsersRouter {
|
func NewUsersRouter(s services.UserService) *UsersRouter {
|
||||||
return &UsersRouter{service: *s}
|
return &UsersRouter{service: s}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (router *UsersRouter) RegisterRoutes(r *gin.RouterGroup) {
|
func (router *UsersRouter) RegisterRoutes(r *gin.RouterGroup) {
|
||||||
@@ -36,15 +36,15 @@ func (router *UsersRouter) RegisterRoutes(r *gin.RouterGroup) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param user body dto.CreateUserRequest true "User info"
|
// @Param user body dto.CreateUserRequest true "User info"
|
||||||
// @Success 201 {object} dto.UserResponse
|
// @Success 201 {object} dto.UserResponse
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} dto.UserErrorResponse
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 500 {object} dto.UserErrorResponse
|
||||||
// @Router /users [post]
|
// @Router /users [post]
|
||||||
func (router *UsersRouter) CreateUser(c *gin.Context) {
|
func (router *UsersRouter) CreateUser(c *gin.Context) {
|
||||||
var req dto.CreateUserRequest
|
var req dto.CreateUserRequest
|
||||||
var resp dto.UserResponse
|
var resp dto.UserResponse
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,15 +65,15 @@ func (router *UsersRouter) CreateUser(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path int true "User ID"
|
// @Param id path int true "User ID"
|
||||||
// @Success 200 {object} dto.UserResponse
|
// @Success 200 {object} dto.UserResponse
|
||||||
// @Failure 400 {object} map[string]string "invalid id"
|
// @Failure 400 {object} dto.UserErrorResponse "invalid id"
|
||||||
// @Failure 404 {object} map[string]string "user not found"
|
// @Failure 404 {object} dto.UserErrorResponse "user not found"
|
||||||
// @Failure 500 {object} map[string]string "internal server error"
|
// @Failure 500 {object} dto.UserErrorResponse "internal server error"
|
||||||
// @Router /users/{id} [get]
|
// @Router /users/{id} [get]
|
||||||
func (router *UsersRouter) GetByID(c *gin.Context) {
|
func (router *UsersRouter) GetByID(c *gin.Context) {
|
||||||
var resp dto.UserResponse
|
var resp dto.UserResponse
|
||||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: "invalid id"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,15 +94,15 @@ func (router *UsersRouter) GetByID(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param telegramId path int true "Telegram ID"
|
// @Param telegramId path int true "Telegram ID"
|
||||||
// @Success 200 {object} dto.UserResponse
|
// @Success 200 {object} dto.UserResponse
|
||||||
// @Failure 400 {object} map[string]string "invalid telegram id"
|
// @Failure 400 {object} dto.UserErrorResponse "invalid telegram id"
|
||||||
// @Failure 404 {object} map[string]string "user not found"
|
// @Failure 404 {object} dto.UserErrorResponse "user not found"
|
||||||
// @Failure 500 {object} map[string]string "internal server error"
|
// @Failure 500 {object} dto.UserErrorResponse "internal server error"
|
||||||
// @Router /users/by-telegram/{telegramId} [get]
|
// @Router /users/by-telegram/{telegramId} [get]
|
||||||
func (router *UsersRouter) GetByTelegramID(c *gin.Context) {
|
func (router *UsersRouter) GetByTelegramID(c *gin.Context) {
|
||||||
var resp dto.UserResponse
|
var resp dto.UserResponse
|
||||||
telegramID, err := strconv.ParseInt(c.Param("telegramId"), 10, 64)
|
telegramID, err := strconv.ParseInt(c.Param("telegramId"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid telegram id"})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: "invalid telegram id"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,21 +124,21 @@ func (router *UsersRouter) GetByTelegramID(c *gin.Context) {
|
|||||||
// @Param id path int true "User ID"
|
// @Param id path int true "User ID"
|
||||||
// @Param user body dto.UpdateUserRequest true "Данные для обновления"
|
// @Param user body dto.UpdateUserRequest true "Данные для обновления"
|
||||||
// @Success 200 {object} dto.UserResponse
|
// @Success 200 {object} dto.UserResponse
|
||||||
// @Failure 400 {object} map[string]string "invalid id or invalid body"
|
// @Failure 400 {object} dto.UserErrorResponse "invalid id or invalid body"
|
||||||
// @Failure 404 {object} map[string]string "user not found"
|
// @Failure 404 {object} dto.UserErrorResponse "user not found"
|
||||||
// @Failure 500 {object} map[string]string "internal server error"
|
// @Failure 500 {object} dto.UserErrorResponse "internal server error"
|
||||||
// @Router /users/{id} [patch]
|
// @Router /users/{id} [patch]
|
||||||
func (router *UsersRouter) Update(c *gin.Context) {
|
func (router *UsersRouter) Update(c *gin.Context) {
|
||||||
var resp dto.UserResponse
|
var resp dto.UserResponse
|
||||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: "invalid id"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req dto.UpdateUserRequest
|
var req dto.UpdateUserRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,14 +159,14 @@ func (router *UsersRouter) Update(c *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path int true "User ID"
|
// @Param id path int true "User ID"
|
||||||
// @Success 204 {string} string "no content"
|
// @Success 204 {string} string "no content"
|
||||||
// @Failure 400 {object} map[string]string "invalid id"
|
// @Failure 400 {object} dto.UserErrorResponse "invalid id"
|
||||||
// @Failure 404 {object} map[string]string "user not found"
|
// @Failure 404 {object} dto.UserErrorResponse "user not found"
|
||||||
// @Failure 500 {object} map[string]string "internal server error"
|
// @Failure 500 {object} dto.UserErrorResponse "internal server error"
|
||||||
// @Router /users/{id} [delete]
|
// @Router /users/{id} [delete]
|
||||||
func (router *UsersRouter) Delete(c *gin.Context) {
|
func (router *UsersRouter) Delete(c *gin.Context) {
|
||||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: "invalid id"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,12 +181,12 @@ func (router *UsersRouter) Delete(c *gin.Context) {
|
|||||||
func handleError(c *gin.Context, err error) {
|
func handleError(c *gin.Context, err error) {
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, services.ErrUserNotFound):
|
case errors.Is(err, services.ErrUserNotFound):
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
c.JSON(http.StatusNotFound, dto.UserErrorResponse{Error: err.Error()})
|
||||||
case errors.Is(err, services.ErrInvalidPatch):
|
case errors.Is(err, services.ErrInvalidPatch):
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: err.Error()})
|
||||||
case errors.Is(err, services.ErrTelegramIDMissing):
|
case errors.Is(err, services.ErrTelegramIDMissing):
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
c.JSON(http.StatusBadRequest, dto.UserErrorResponse{Error: err.Error()})
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
|
c.JSON(http.StatusInternalServerError, dto.UserErrorResponse{Error: "internal server error"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,344 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/api/dto"
|
||||||
|
"FamilyHub/src/api/services"
|
||||||
|
"FamilyHub/src/domain"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type userServiceMock struct {
|
||||||
|
createFn func(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error)
|
||||||
|
getByIDFn func(ctx context.Context, id int64) (*domain.User, error)
|
||||||
|
getByTelegramIDFn func(ctx context.Context, telegramID int64) (*domain.User, error)
|
||||||
|
updateFn func(ctx context.Context, id int64, req dto.UpdateUserRequest) (*domain.User, error)
|
||||||
|
deleteFn func(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *userServiceMock) Create(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error) {
|
||||||
|
if m.createFn != nil {
|
||||||
|
return m.createFn(ctx, req)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock create is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *userServiceMock) GetByID(ctx context.Context, id int64) (*domain.User, error) {
|
||||||
|
if m.getByIDFn != nil {
|
||||||
|
return m.getByIDFn(ctx, id)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock getByID is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *userServiceMock) GetByTelegramID(ctx context.Context, telegramID int64) (*domain.User, error) {
|
||||||
|
if m.getByTelegramIDFn != nil {
|
||||||
|
return m.getByTelegramIDFn(ctx, telegramID)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock getByTelegramID is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *userServiceMock) Update(ctx context.Context, id int64, req dto.UpdateUserRequest) (*domain.User, error) {
|
||||||
|
if m.updateFn != nil {
|
||||||
|
return m.updateFn(ctx, id, req)
|
||||||
|
}
|
||||||
|
return nil, errors.New("mock update is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *userServiceMock) Delete(ctx context.Context, id int64) error {
|
||||||
|
if m.deleteFn != nil {
|
||||||
|
return m.deleteFn(ctx, id)
|
||||||
|
}
|
||||||
|
return errors.New("mock delete is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupUsersRouter(mock services.UserService) *gin.Engine {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
r := gin.New()
|
||||||
|
apiV1 := r.Group("/api/v1")
|
||||||
|
router := NewUsersRouter(mock)
|
||||||
|
router.RegisterRoutes(apiV1)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleUser() *domain.User {
|
||||||
|
username := "john"
|
||||||
|
lastName := "Doe"
|
||||||
|
languageCode := "en"
|
||||||
|
|
||||||
|
return &domain.User{
|
||||||
|
ID: 10,
|
||||||
|
TelegramID: 100500,
|
||||||
|
Username: &username,
|
||||||
|
FirstName: "John",
|
||||||
|
LastName: &lastName,
|
||||||
|
LanguageCode: &languageCode,
|
||||||
|
CreatedAt: time.Date(2026, time.January, 21, 10, 11, 12, 0, time.UTC),
|
||||||
|
UpdatedAt: time.Date(2026, time.January, 21, 12, 11, 12, 0, time.UTC),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_CreateUser(t *testing.T) {
|
||||||
|
t.Run("bad request on malformed body", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad request on domain validation error", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{createFn: func(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error) {
|
||||||
|
return nil, services.ErrTelegramIDMissing
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":1,"first_name":"A"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), services.ErrTelegramIDMissing.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("created", func(t *testing.T) {
|
||||||
|
expected := sampleUser()
|
||||||
|
r := setupUsersRouter(&userServiceMock{createFn: func(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error) {
|
||||||
|
assert.Equal(t, int64(100500), req.TelegramID)
|
||||||
|
assert.Equal(t, "John", req.FirstName)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":100500,"first_name":"John"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "100500")
|
||||||
|
assert.Contains(t, w.Body.String(), "John")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_GetByID(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/abc", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not found", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.User, error) {
|
||||||
|
return nil, services.ErrUserNotFound
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/1", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), services.ErrUserNotFound.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ok", func(t *testing.T) {
|
||||||
|
expected := sampleUser()
|
||||||
|
r := setupUsersRouter(&userServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.User, error) {
|
||||||
|
assert.Equal(t, int64(10), id)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/10", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "100500")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_GetByTelegramID(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid telegram id", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-telegram/abc", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid telegram id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ok", func(t *testing.T) {
|
||||||
|
expected := sampleUser()
|
||||||
|
r := setupUsersRouter(&userServiceMock{getByTelegramIDFn: func(ctx context.Context, telegramID int64) (*domain.User, error) {
|
||||||
|
assert.Equal(t, int64(100500), telegramID)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-telegram/100500", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "John")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_Update(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/abc", bytes.NewBufferString(`{"first_name":"John"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad request on malformed body", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/10", bytes.NewBufferString(`{"first_name":`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "error")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad request on invalid patch", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{updateFn: func(ctx context.Context, id int64, req dto.UpdateUserRequest) (*domain.User, error) {
|
||||||
|
return nil, services.ErrInvalidPatch
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/10", bytes.NewBufferString(`{"first_name":"John"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), services.ErrInvalidPatch.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ok", func(t *testing.T) {
|
||||||
|
expected := sampleUser()
|
||||||
|
r := setupUsersRouter(&userServiceMock{updateFn: func(ctx context.Context, id int64, req dto.UpdateUserRequest) (*domain.User, error) {
|
||||||
|
assert.Equal(t, int64(10), id)
|
||||||
|
require.NotNil(t, req.FirstName)
|
||||||
|
assert.Equal(t, "John", *req.FirstName)
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/10", bytes.NewBufferString(`{"first_name":"John"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "100500")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_Delete(t *testing.T) {
|
||||||
|
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{})
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/users/abc", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "invalid id")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not found", func(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{deleteFn: func(ctx context.Context, id int64) error {
|
||||||
|
return services.ErrUserNotFound
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/users/1", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), services.ErrUserNotFound.Error())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no content", func(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
r := setupUsersRouter(&userServiceMock{deleteFn: func(ctx context.Context, id int64) error {
|
||||||
|
called = true
|
||||||
|
assert.Equal(t, int64(10), id)
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
req := httptest.NewRequest(http.MethodDelete, "/api/v1/users/10", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusNoContent, w.Code)
|
||||||
|
assert.True(t, called)
|
||||||
|
assert.Empty(t, strings.TrimSpace(w.Body.String()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_CreateUser_ResponseShape(t *testing.T) {
|
||||||
|
expected := sampleUser()
|
||||||
|
r := setupUsersRouter(&userServiceMock{createFn: func(ctx context.Context, req dto.CreateUserRequest) (*domain.User, error) {
|
||||||
|
return expected, nil
|
||||||
|
}})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":100500,"first_name":"John"}`))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusCreated, w.Code)
|
||||||
|
var resp dto.UserResponse
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expected.ID, resp.ID)
|
||||||
|
assert.Equal(t, expected.TelegramID, resp.TelegramID)
|
||||||
|
assert.Equal(t, expected.FirstName, resp.FirstName)
|
||||||
|
assert.Equal(t, expected.CreatedAt.Format(time.RFC3339), resp.CreatedAt)
|
||||||
|
assert.Equal(t, expected.UpdatedAt.Format(time.RFC3339), resp.UpdatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUsersRouter_GetByID_UsesPathID(t *testing.T) {
|
||||||
|
r := setupUsersRouter(&userServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.User, error) {
|
||||||
|
assert.Equal(t, int64(42), id)
|
||||||
|
u := sampleUser()
|
||||||
|
u.ID = id
|
||||||
|
return u, nil
|
||||||
|
}})
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/42", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), strconv.FormatInt(42, 10))
|
||||||
|
}
|
||||||
+6
-1
@@ -48,9 +48,14 @@ func NewServer(cfg config.Config) *Server {
|
|||||||
|
|
||||||
usersRepo := repositories.NewUsersSQLRepository(dbConn)
|
usersRepo := repositories.NewUsersSQLRepository(dbConn)
|
||||||
usersService := services.NewUserService(usersRepo)
|
usersService := services.NewUserService(usersRepo)
|
||||||
usersRouter := routers.NewUsersRouter(&usersService)
|
usersRouter := routers.NewUsersRouter(usersService)
|
||||||
usersRouter.RegisterRoutes(apiV1)
|
usersRouter.RegisterRoutes(apiV1)
|
||||||
|
|
||||||
|
familyRepo := repositories.NewFamilySQLRepository(dbConn)
|
||||||
|
familyService := services.NewFamilyService(familyRepo)
|
||||||
|
familyRouter := routers.NewFamiliesRouter(familyService)
|
||||||
|
familyRouter.RegisterRoutes(apiV1)
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
httpServer: &http.Server{
|
httpServer: &http.Server{
|
||||||
Addr: cfg.APIHost + ":" + cfg.APIPort,
|
Addr: cfg.APIHost + ":" + cfg.APIPort,
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/api/dto"
|
||||||
|
"FamilyHub/src/domain"
|
||||||
|
"FamilyHub/src/repositories"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FamilyService interface {
|
||||||
|
Create(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error)
|
||||||
|
GetByID(ctx context.Context, id int64) (*domain.Family, error)
|
||||||
|
Update(ctx context.Context, id int64, req dto.UpdateFamilyRequest) (*domain.Family, error)
|
||||||
|
Delete(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type familyService struct {
|
||||||
|
repo repositories.FamilyRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFamilyService(repo repositories.FamilyRepository) FamilyService {
|
||||||
|
return &familyService{repo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFamilyNotFound = errors.New("family not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *familyService) Create(ctx context.Context, req dto.CreateFamilyRequest) (*domain.Family, error) {
|
||||||
|
family_ := &domain.Family{
|
||||||
|
Name: req.Name,
|
||||||
|
OwnerID: req.OwnerID,
|
||||||
|
TelegramChatID: req.TelegramChatID,
|
||||||
|
TelegramChatName: req.TelegramChatName,
|
||||||
|
}
|
||||||
|
if err := s.repo.Create(ctx, family_); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return family_, nil
|
||||||
|
}
|
||||||
|
func (s *familyService) GetByID(ctx context.Context, id int64) (*domain.Family, error) {
|
||||||
|
family_, err := s.repo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if family_ == nil {
|
||||||
|
return nil, ErrFamilyNotFound
|
||||||
|
}
|
||||||
|
return family_, nil
|
||||||
|
}
|
||||||
|
func (s *familyService) Update(ctx context.Context, id int64, req dto.UpdateFamilyRequest) (*domain.Family, error) {
|
||||||
|
existing, err := s.repo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
return nil, ErrFamilyNotFound
|
||||||
|
}
|
||||||
|
if err := s.repo.Update(ctx, &domain.Family{
|
||||||
|
ID: id,
|
||||||
|
Name: *req.Name,
|
||||||
|
OwnerID: existing.OwnerID,
|
||||||
|
TelegramChatID: existing.TelegramChatID,
|
||||||
|
TelegramChatName: req.TelegramChatName,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.repo.GetByID(ctx, id)
|
||||||
|
}
|
||||||
|
func (s *familyService) Delete(ctx context.Context, id int64) error {
|
||||||
|
if err := s.repo.Delete(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Family struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
OwnerID int64
|
||||||
|
TelegramChatID int64
|
||||||
|
TelegramChatName string
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type FamilyRole string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FamilyRoleOwner FamilyRole = "owner"
|
||||||
|
FamilyRoleAdmin FamilyRole = "admin"
|
||||||
|
FamilyRoleMember FamilyRole = "member"
|
||||||
|
FamilyRoleChild FamilyRole = "child"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FamilyMember struct {
|
||||||
|
ID int64
|
||||||
|
FamilyID int64
|
||||||
|
UserID int64
|
||||||
|
Role FamilyRole
|
||||||
|
JoinedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type FamilyThread struct {
|
||||||
|
ID int64
|
||||||
|
FamilyID int64
|
||||||
|
Type string
|
||||||
|
Title string
|
||||||
|
TelegramTopicID int64
|
||||||
|
IsSystem bool
|
||||||
|
CreatedBy int64
|
||||||
|
CreatedAt time.Time
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
ID int64
|
|
||||||
TelegramID int64
|
|
||||||
Username *string
|
|
||||||
FirstName string
|
|
||||||
LastName *string
|
|
||||||
LanguageCode *string
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type TelegramChat struct {
|
|
||||||
ID int64
|
|
||||||
TelegramID int64
|
|
||||||
Title string
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Family struct {
|
|
||||||
ID int64
|
|
||||||
Name string
|
|
||||||
OwnerID int64
|
|
||||||
TelegramChatID int64
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type FamilyRole string
|
|
||||||
|
|
||||||
const (
|
|
||||||
FamilyRoleOwner FamilyRole = "owner"
|
|
||||||
FamilyRoleAdmin FamilyRole = "admin"
|
|
||||||
FamilyRoleMember FamilyRole = "member"
|
|
||||||
FamilyRoleChild FamilyRole = "child"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FamilyMember struct {
|
|
||||||
ID int64
|
|
||||||
FamilyID int64
|
|
||||||
UserID int64
|
|
||||||
Role FamilyRole
|
|
||||||
JoinedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThreadType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ThreadExpenses ThreadType = "expenses"
|
|
||||||
ThreadMovies ThreadType = "movies"
|
|
||||||
ThreadSchedule ThreadType = "schedule"
|
|
||||||
ThreadRecipes ThreadType = "recipes"
|
|
||||||
ThreadCustom ThreadType = "custom"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Thread struct {
|
|
||||||
ID int64
|
|
||||||
FamilyID int64
|
|
||||||
Type ThreadType
|
|
||||||
Title string
|
|
||||||
TelegramTopicID int64
|
|
||||||
IsSystem bool
|
|
||||||
CreatedBy int64
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"FamilyHub/src/domain"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FamilyRepository interface {
|
||||||
|
Create(ctx context.Context, family *domain.Family) error
|
||||||
|
GetByID(ctx context.Context, id int64) (*domain.Family, error)
|
||||||
|
Update(ctx context.Context, family *domain.Family) error
|
||||||
|
Delete(ctx context.Context, id int64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FamilySQLRepository struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFamilySQLRepository(db *sql.DB) *FamilySQLRepository {
|
||||||
|
return &FamilySQLRepository{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FamilySQLRepository) Create(ctx context.Context, family *domain.Family) error {
|
||||||
|
query := `
|
||||||
|
INSERT INTO families
|
||||||
|
(name, owner_id, telegram_chat_id, telegram_chat_name, created_at, updated_at)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
RETURNING id, created_at, updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
return r.db.QueryRowContext(
|
||||||
|
ctx,
|
||||||
|
query,
|
||||||
|
family.Name,
|
||||||
|
family.OwnerID,
|
||||||
|
family.TelegramChatID,
|
||||||
|
family.TelegramChatName,
|
||||||
|
family.CreatedAt,
|
||||||
|
family.UpdatedAt,
|
||||||
|
).Scan(&family.ID, &family.CreatedAt, &family.UpdatedAt)
|
||||||
|
}
|
||||||
|
func (r *FamilySQLRepository) GetByID(ctx context.Context, id int64) (*domain.Family, error) {
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
owner_id,
|
||||||
|
telegram_chat_id,
|
||||||
|
telegram_chat_name,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM families
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
var family domain.Family
|
||||||
|
|
||||||
|
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
||||||
|
&family.ID,
|
||||||
|
&family.Name,
|
||||||
|
&family.OwnerID,
|
||||||
|
&family.TelegramChatID,
|
||||||
|
&family.TelegramChatName,
|
||||||
|
&family.CreatedAt,
|
||||||
|
&family.UpdatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, nil // или кастомную ErrNotFound
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &family, nil
|
||||||
|
}
|
||||||
|
func (r *FamilySQLRepository) Update(ctx context.Context, family *domain.Family) error {
|
||||||
|
query := `
|
||||||
|
UPDATE families SET
|
||||||
|
name = $1,
|
||||||
|
telegram_chat_id = $2,
|
||||||
|
telegram_chat_name = $3,
|
||||||
|
updated_at = now()
|
||||||
|
WHERE id = $4
|
||||||
|
RETURNING updated_at
|
||||||
|
`
|
||||||
|
|
||||||
|
return r.db.QueryRowContext(
|
||||||
|
ctx,
|
||||||
|
query,
|
||||||
|
family.Name,
|
||||||
|
family.TelegramChatID,
|
||||||
|
family.TelegramChatName,
|
||||||
|
family.UpdatedAt,
|
||||||
|
family.ID,
|
||||||
|
).Scan(&family.UpdatedAt)
|
||||||
|
}
|
||||||
|
func (r *FamilySQLRepository) Delete(ctx context.Context, id int64) error {
|
||||||
|
query := `DELETE FROM families WHERE id = $1`
|
||||||
|
|
||||||
|
result, err := r.db.ExecContext(ctx, query, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rows == 0 {
|
||||||
|
return sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user