diff --git a/backend/app/api/docs/docs.go b/backend/app/api/docs/docs.go index 17af262..c420603 100644 --- a/backend/app/api/docs/docs.go +++ b/backend/app/api/docs/docs.go @@ -49,7 +49,7 @@ const docTemplate = `{ "items": { "type": "array", "items": { - "$ref": "#/definitions/types.ItemSummary" + "$ref": "#/definitions/repo.ItemSummary" } } } @@ -79,7 +79,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.ItemCreate" + "$ref": "#/definitions/repo.ItemCreate" } } ], @@ -87,7 +87,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemSummary" + "$ref": "#/definitions/repo.ItemSummary" } } } @@ -150,7 +150,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } } } @@ -182,7 +182,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.ItemUpdate" + "$ref": "#/definitions/repo.ItemUpdate" } } ], @@ -190,7 +190,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } } } @@ -272,7 +272,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } }, "422": { @@ -358,7 +358,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemAttachmentToken" + "$ref": "#/definitions/v1.ItemAttachmentToken" } } } @@ -394,7 +394,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.ItemAttachmentUpdate" + "$ref": "#/definitions/repo.ItemAttachmentUpdate" } } ], @@ -402,7 +402,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } } } @@ -468,7 +468,7 @@ const docTemplate = `{ "items": { "type": "array", "items": { - "$ref": "#/definitions/types.LabelOut" + "$ref": "#/definitions/repo.LabelOut" } } } @@ -498,7 +498,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.LabelCreate" + "$ref": "#/definitions/repo.LabelCreate" } } ], @@ -506,7 +506,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LabelSummary" + "$ref": "#/definitions/repo.LabelSummary" } } } @@ -539,7 +539,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LabelOut" + "$ref": "#/definitions/repo.LabelOut" } } } @@ -570,7 +570,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LabelOut" + "$ref": "#/definitions/repo.LabelOut" } } } @@ -632,7 +632,7 @@ const docTemplate = `{ "items": { "type": "array", "items": { - "$ref": "#/definitions/types.LocationCount" + "$ref": "#/definitions/repo.LocationOutCount" } } } @@ -662,7 +662,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.LocationCreate" + "$ref": "#/definitions/repo.LocationCreate" } } ], @@ -670,7 +670,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LocationSummary" + "$ref": "#/definitions/repo.LocationSummary" } } } @@ -703,7 +703,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LocationOut" + "$ref": "#/definitions/repo.LocationOut" } } } @@ -734,7 +734,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LocationOut" + "$ref": "#/definitions/repo.LocationOut" } } } @@ -781,7 +781,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ApiSummary" + "$ref": "#/definitions/v1.ApiSummary" } } } @@ -820,7 +820,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.TokenResponse" + "$ref": "#/definitions/v1.TokenResponse" } } } @@ -879,7 +879,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.UserRegistration" + "$ref": "#/definitions/services.UserRegistration" } } ], @@ -916,7 +916,7 @@ const docTemplate = `{ "type": "object", "properties": { "item": { - "$ref": "#/definitions/types.UserOut" + "$ref": "#/definitions/repo.UserOut" } } } @@ -945,7 +945,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.UserUpdate" + "$ref": "#/definitions/repo.UserUpdate" } } ], @@ -961,7 +961,7 @@ const docTemplate = `{ "type": "object", "properties": { "item": { - "$ref": "#/definitions/types.UserUpdate" + "$ref": "#/definitions/repo.UserUpdate" } } } @@ -1013,6 +1013,451 @@ const docTemplate = `{ } }, "definitions": { + "repo.DocumentOut": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "repo.ItemAttachment": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "document": { + "$ref": "#/definitions/repo.DocumentOut" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.ItemAttachmentUpdate": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "repo.ItemCreate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "locationId": { + "description": "Edges", + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "repo.ItemOut": { + "type": "object", + "properties": { + "attachments": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemAttachment" + } + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "insured": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.LabelSummary" + } + }, + "lifetimeWarranty": { + "description": "Warranty", + "type": "boolean" + }, + "location": { + "description": "Edges", + "$ref": "#/definitions/repo.LocationSummary" + }, + "manufacturer": { + "type": "string" + }, + "modelNumber": { + "type": "string" + }, + "name": { + "type": "string" + }, + "notes": { + "description": "Extras", + "type": "string" + }, + "purchaseFrom": { + "type": "string" + }, + "purchasePrice": { + "type": "string", + "example": "0" + }, + "purchaseTime": { + "description": "Purchase", + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "serialNumber": { + "type": "string" + }, + "soldNotes": { + "type": "string" + }, + "soldPrice": { + "type": "string", + "example": "0" + }, + "soldTime": { + "description": "Sold", + "type": "string" + }, + "soldTo": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "warrantyDetails": { + "type": "string" + }, + "warrantyExpires": { + "type": "string" + } + } + }, + "repo.ItemSummary": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "insured": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.LabelSummary" + } + }, + "location": { + "description": "Edges", + "$ref": "#/definitions/repo.LocationSummary" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.ItemUpdate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "insured": { + "type": "boolean" + }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "lifetimeWarranty": { + "description": "Warranty", + "type": "boolean" + }, + "locationId": { + "description": "Edges", + "type": "string" + }, + "manufacturer": { + "type": "string" + }, + "modelNumber": { + "type": "string" + }, + "name": { + "type": "string" + }, + "notes": { + "description": "Extras", + "type": "string" + }, + "purchaseFrom": { + "type": "string" + }, + "purchasePrice": { + "type": "string", + "example": "0" + }, + "purchaseTime": { + "description": "Purchase", + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "serialNumber": { + "description": "Identifications", + "type": "string" + }, + "soldNotes": { + "type": "string" + }, + "soldPrice": { + "type": "string", + "example": "0" + }, + "soldTime": { + "description": "Sold", + "type": "string" + }, + "soldTo": { + "type": "string" + }, + "warrantyDetails": { + "type": "string" + }, + "warrantyExpires": { + "type": "string" + } + } + }, + "repo.LabelCreate": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "repo.LabelOut": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemSummary" + } + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LabelSummary": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LocationCreate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "repo.LocationOut": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemSummary" + } + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LocationOutCount": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "itemCount": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LocationSummary": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.UserOut": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isSuperuser": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "repo.UserUpdate": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "server.Result": { "type": "object", "properties": { @@ -1045,11 +1490,28 @@ const docTemplate = `{ } } }, - "types.ApiSummary": { + "services.UserRegistration": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "v1.ApiSummary": { "type": "object", "properties": { "build": { - "$ref": "#/definitions/types.Build" + "$ref": "#/definitions/v1.Build" }, "health": { "type": "boolean" @@ -1068,7 +1530,7 @@ const docTemplate = `{ } } }, - "types.Build": { + "v1.Build": { "type": "object", "properties": { "buildTime": { @@ -1082,41 +1544,7 @@ const docTemplate = `{ } } }, - "types.DocumentOut": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "path": { - "type": "string" - }, - "title": { - "type": "string" - } - } - }, - "types.ItemAttachment": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "document": { - "$ref": "#/definitions/types.DocumentOut" - }, - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.ItemAttachmentToken": { + "v1.ItemAttachmentToken": { "type": "object", "properties": { "token": { @@ -1124,434 +1552,7 @@ const docTemplate = `{ } } }, - "types.ItemAttachmentUpdate": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - } - }, - "types.ItemCreate": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "labelIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "locationId": { - "description": "Edges", - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "types.ItemOut": { - "type": "object", - "properties": { - "attachments": { - "type": "array", - "items": { - "$ref": "#/definitions/types.ItemAttachment" - } - }, - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "insured": { - "type": "boolean" - }, - "labels": { - "type": "array", - "items": { - "$ref": "#/definitions/types.LabelSummary" - } - }, - "lifetimeWarranty": { - "description": "Warranty", - "type": "boolean" - }, - "location": { - "description": "Edges", - "$ref": "#/definitions/types.LocationSummary" - }, - "manufacturer": { - "type": "string" - }, - "modelNumber": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notes": { - "description": "Extras", - "type": "string" - }, - "purchaseFrom": { - "type": "string" - }, - "purchasePrice": { - "type": "string", - "example": "0" - }, - "purchaseTime": { - "description": "Purchase", - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "serialNumber": { - "description": "Identifications", - "type": "string" - }, - "soldNotes": { - "type": "string" - }, - "soldPrice": { - "type": "string", - "example": "0" - }, - "soldTime": { - "description": "Sold", - "type": "string" - }, - "soldTo": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "warrantyDetails": { - "type": "string" - }, - "warrantyExpires": { - "type": "string" - } - } - }, - "types.ItemSummary": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "insured": { - "type": "boolean" - }, - "labels": { - "type": "array", - "items": { - "$ref": "#/definitions/types.LabelSummary" - } - }, - "lifetimeWarranty": { - "description": "Warranty", - "type": "boolean" - }, - "location": { - "description": "Edges", - "$ref": "#/definitions/types.LocationSummary" - }, - "manufacturer": { - "type": "string" - }, - "modelNumber": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notes": { - "description": "Extras", - "type": "string" - }, - "purchaseFrom": { - "type": "string" - }, - "purchasePrice": { - "type": "string", - "example": "0" - }, - "purchaseTime": { - "description": "Purchase", - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "serialNumber": { - "description": "Identifications", - "type": "string" - }, - "soldNotes": { - "type": "string" - }, - "soldPrice": { - "type": "string", - "example": "0" - }, - "soldTime": { - "description": "Sold", - "type": "string" - }, - "soldTo": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "warrantyDetails": { - "type": "string" - }, - "warrantyExpires": { - "type": "string" - } - } - }, - "types.ItemUpdate": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "insured": { - "type": "boolean" - }, - "labelIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "lifetimeWarranty": { - "description": "Warranty", - "type": "boolean" - }, - "locationId": { - "description": "Edges", - "type": "string" - }, - "manufacturer": { - "type": "string" - }, - "modelNumber": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notes": { - "description": "Extras", - "type": "string" - }, - "purchaseFrom": { - "type": "string" - }, - "purchasePrice": { - "type": "string", - "example": "0" - }, - "purchaseTime": { - "description": "Purchase", - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "serialNumber": { - "description": "Identifications", - "type": "string" - }, - "soldNotes": { - "type": "string" - }, - "soldPrice": { - "type": "string", - "example": "0" - }, - "soldTime": { - "description": "Sold", - "type": "string" - }, - "soldTo": { - "type": "string" - }, - "warrantyDetails": { - "type": "string" - }, - "warrantyExpires": { - "type": "string" - } - } - }, - "types.LabelCreate": { - "type": "object", - "properties": { - "color": { - "type": "string" - }, - "description": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "types.LabelOut": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/types.ItemSummary" - } - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LabelSummary": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LocationCount": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "itemCount": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LocationCreate": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "types.LocationOut": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/types.ItemSummary" - } - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LocationSummary": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.TokenResponse": { + "v1.TokenResponse": { "type": "object", "properties": { "expiresAt": { @@ -1561,65 +1562,6 @@ const docTemplate = `{ "type": "string" } } - }, - "types.UserIn": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "name": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "types.UserOut": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "groupId": { - "type": "string" - }, - "groupName": { - "type": "string" - }, - "id": { - "type": "string" - }, - "isSuperuser": { - "type": "boolean" - }, - "name": { - "type": "string" - } - } - }, - "types.UserRegistration": { - "type": "object", - "properties": { - "groupName": { - "type": "string" - }, - "user": { - "$ref": "#/definitions/types.UserIn" - } - } - }, - "types.UserUpdate": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "name": { - "type": "string" - } - } } }, "securityDefinitions": { diff --git a/backend/app/api/docs/swagger.json b/backend/app/api/docs/swagger.json index 87ae636..bae7858 100644 --- a/backend/app/api/docs/swagger.json +++ b/backend/app/api/docs/swagger.json @@ -41,7 +41,7 @@ "items": { "type": "array", "items": { - "$ref": "#/definitions/types.ItemSummary" + "$ref": "#/definitions/repo.ItemSummary" } } } @@ -71,7 +71,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.ItemCreate" + "$ref": "#/definitions/repo.ItemCreate" } } ], @@ -79,7 +79,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemSummary" + "$ref": "#/definitions/repo.ItemSummary" } } } @@ -142,7 +142,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } } } @@ -174,7 +174,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.ItemUpdate" + "$ref": "#/definitions/repo.ItemUpdate" } } ], @@ -182,7 +182,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } } } @@ -264,7 +264,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } }, "422": { @@ -350,7 +350,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemAttachmentToken" + "$ref": "#/definitions/v1.ItemAttachmentToken" } } } @@ -386,7 +386,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.ItemAttachmentUpdate" + "$ref": "#/definitions/repo.ItemAttachmentUpdate" } } ], @@ -394,7 +394,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ItemOut" + "$ref": "#/definitions/repo.ItemOut" } } } @@ -460,7 +460,7 @@ "items": { "type": "array", "items": { - "$ref": "#/definitions/types.LabelOut" + "$ref": "#/definitions/repo.LabelOut" } } } @@ -490,7 +490,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.LabelCreate" + "$ref": "#/definitions/repo.LabelCreate" } } ], @@ -498,7 +498,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LabelSummary" + "$ref": "#/definitions/repo.LabelSummary" } } } @@ -531,7 +531,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LabelOut" + "$ref": "#/definitions/repo.LabelOut" } } } @@ -562,7 +562,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LabelOut" + "$ref": "#/definitions/repo.LabelOut" } } } @@ -624,7 +624,7 @@ "items": { "type": "array", "items": { - "$ref": "#/definitions/types.LocationCount" + "$ref": "#/definitions/repo.LocationOutCount" } } } @@ -654,7 +654,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.LocationCreate" + "$ref": "#/definitions/repo.LocationCreate" } } ], @@ -662,7 +662,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LocationSummary" + "$ref": "#/definitions/repo.LocationSummary" } } } @@ -695,7 +695,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LocationOut" + "$ref": "#/definitions/repo.LocationOut" } } } @@ -726,7 +726,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.LocationOut" + "$ref": "#/definitions/repo.LocationOut" } } } @@ -773,7 +773,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.ApiSummary" + "$ref": "#/definitions/v1.ApiSummary" } } } @@ -812,7 +812,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/types.TokenResponse" + "$ref": "#/definitions/v1.TokenResponse" } } } @@ -871,7 +871,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.UserRegistration" + "$ref": "#/definitions/services.UserRegistration" } } ], @@ -908,7 +908,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/definitions/types.UserOut" + "$ref": "#/definitions/repo.UserOut" } } } @@ -937,7 +937,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/types.UserUpdate" + "$ref": "#/definitions/repo.UserUpdate" } } ], @@ -953,7 +953,7 @@ "type": "object", "properties": { "item": { - "$ref": "#/definitions/types.UserUpdate" + "$ref": "#/definitions/repo.UserUpdate" } } } @@ -1005,6 +1005,451 @@ } }, "definitions": { + "repo.DocumentOut": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "repo.ItemAttachment": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "document": { + "$ref": "#/definitions/repo.DocumentOut" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.ItemAttachmentUpdate": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + } + }, + "repo.ItemCreate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "locationId": { + "description": "Edges", + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "repo.ItemOut": { + "type": "object", + "properties": { + "attachments": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemAttachment" + } + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "insured": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.LabelSummary" + } + }, + "lifetimeWarranty": { + "description": "Warranty", + "type": "boolean" + }, + "location": { + "description": "Edges", + "$ref": "#/definitions/repo.LocationSummary" + }, + "manufacturer": { + "type": "string" + }, + "modelNumber": { + "type": "string" + }, + "name": { + "type": "string" + }, + "notes": { + "description": "Extras", + "type": "string" + }, + "purchaseFrom": { + "type": "string" + }, + "purchasePrice": { + "type": "string", + "example": "0" + }, + "purchaseTime": { + "description": "Purchase", + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "serialNumber": { + "type": "string" + }, + "soldNotes": { + "type": "string" + }, + "soldPrice": { + "type": "string", + "example": "0" + }, + "soldTime": { + "description": "Sold", + "type": "string" + }, + "soldTo": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "warrantyDetails": { + "type": "string" + }, + "warrantyExpires": { + "type": "string" + } + } + }, + "repo.ItemSummary": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "insured": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.LabelSummary" + } + }, + "location": { + "description": "Edges", + "$ref": "#/definitions/repo.LocationSummary" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.ItemUpdate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "insured": { + "type": "boolean" + }, + "labelIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "lifetimeWarranty": { + "description": "Warranty", + "type": "boolean" + }, + "locationId": { + "description": "Edges", + "type": "string" + }, + "manufacturer": { + "type": "string" + }, + "modelNumber": { + "type": "string" + }, + "name": { + "type": "string" + }, + "notes": { + "description": "Extras", + "type": "string" + }, + "purchaseFrom": { + "type": "string" + }, + "purchasePrice": { + "type": "string", + "example": "0" + }, + "purchaseTime": { + "description": "Purchase", + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "serialNumber": { + "description": "Identifications", + "type": "string" + }, + "soldNotes": { + "type": "string" + }, + "soldPrice": { + "type": "string", + "example": "0" + }, + "soldTime": { + "description": "Sold", + "type": "string" + }, + "soldTo": { + "type": "string" + }, + "warrantyDetails": { + "type": "string" + }, + "warrantyExpires": { + "type": "string" + } + } + }, + "repo.LabelCreate": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "repo.LabelOut": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemSummary" + } + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LabelSummary": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LocationCreate": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "repo.LocationOut": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/repo.ItemSummary" + } + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LocationOutCount": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "itemCount": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.LocationSummary": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + } + }, + "repo.UserOut": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isSuperuser": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "repo.UserUpdate": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, "server.Result": { "type": "object", "properties": { @@ -1037,11 +1482,28 @@ } } }, - "types.ApiSummary": { + "services.UserRegistration": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "v1.ApiSummary": { "type": "object", "properties": { "build": { - "$ref": "#/definitions/types.Build" + "$ref": "#/definitions/v1.Build" }, "health": { "type": "boolean" @@ -1060,7 +1522,7 @@ } } }, - "types.Build": { + "v1.Build": { "type": "object", "properties": { "buildTime": { @@ -1074,41 +1536,7 @@ } } }, - "types.DocumentOut": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "path": { - "type": "string" - }, - "title": { - "type": "string" - } - } - }, - "types.ItemAttachment": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "document": { - "$ref": "#/definitions/types.DocumentOut" - }, - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.ItemAttachmentToken": { + "v1.ItemAttachmentToken": { "type": "object", "properties": { "token": { @@ -1116,434 +1544,7 @@ } } }, - "types.ItemAttachmentUpdate": { - "type": "object", - "properties": { - "title": { - "type": "string" - }, - "type": { - "type": "string" - } - } - }, - "types.ItemCreate": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "labelIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "locationId": { - "description": "Edges", - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "types.ItemOut": { - "type": "object", - "properties": { - "attachments": { - "type": "array", - "items": { - "$ref": "#/definitions/types.ItemAttachment" - } - }, - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "insured": { - "type": "boolean" - }, - "labels": { - "type": "array", - "items": { - "$ref": "#/definitions/types.LabelSummary" - } - }, - "lifetimeWarranty": { - "description": "Warranty", - "type": "boolean" - }, - "location": { - "description": "Edges", - "$ref": "#/definitions/types.LocationSummary" - }, - "manufacturer": { - "type": "string" - }, - "modelNumber": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notes": { - "description": "Extras", - "type": "string" - }, - "purchaseFrom": { - "type": "string" - }, - "purchasePrice": { - "type": "string", - "example": "0" - }, - "purchaseTime": { - "description": "Purchase", - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "serialNumber": { - "description": "Identifications", - "type": "string" - }, - "soldNotes": { - "type": "string" - }, - "soldPrice": { - "type": "string", - "example": "0" - }, - "soldTime": { - "description": "Sold", - "type": "string" - }, - "soldTo": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "warrantyDetails": { - "type": "string" - }, - "warrantyExpires": { - "type": "string" - } - } - }, - "types.ItemSummary": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "insured": { - "type": "boolean" - }, - "labels": { - "type": "array", - "items": { - "$ref": "#/definitions/types.LabelSummary" - } - }, - "lifetimeWarranty": { - "description": "Warranty", - "type": "boolean" - }, - "location": { - "description": "Edges", - "$ref": "#/definitions/types.LocationSummary" - }, - "manufacturer": { - "type": "string" - }, - "modelNumber": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notes": { - "description": "Extras", - "type": "string" - }, - "purchaseFrom": { - "type": "string" - }, - "purchasePrice": { - "type": "string", - "example": "0" - }, - "purchaseTime": { - "description": "Purchase", - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "serialNumber": { - "description": "Identifications", - "type": "string" - }, - "soldNotes": { - "type": "string" - }, - "soldPrice": { - "type": "string", - "example": "0" - }, - "soldTime": { - "description": "Sold", - "type": "string" - }, - "soldTo": { - "type": "string" - }, - "updatedAt": { - "type": "string" - }, - "warrantyDetails": { - "type": "string" - }, - "warrantyExpires": { - "type": "string" - } - } - }, - "types.ItemUpdate": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "insured": { - "type": "boolean" - }, - "labelIds": { - "type": "array", - "items": { - "type": "string" - } - }, - "lifetimeWarranty": { - "description": "Warranty", - "type": "boolean" - }, - "locationId": { - "description": "Edges", - "type": "string" - }, - "manufacturer": { - "type": "string" - }, - "modelNumber": { - "type": "string" - }, - "name": { - "type": "string" - }, - "notes": { - "description": "Extras", - "type": "string" - }, - "purchaseFrom": { - "type": "string" - }, - "purchasePrice": { - "type": "string", - "example": "0" - }, - "purchaseTime": { - "description": "Purchase", - "type": "string" - }, - "quantity": { - "type": "integer" - }, - "serialNumber": { - "description": "Identifications", - "type": "string" - }, - "soldNotes": { - "type": "string" - }, - "soldPrice": { - "type": "string", - "example": "0" - }, - "soldTime": { - "description": "Sold", - "type": "string" - }, - "soldTo": { - "type": "string" - }, - "warrantyDetails": { - "type": "string" - }, - "warrantyExpires": { - "type": "string" - } - } - }, - "types.LabelCreate": { - "type": "object", - "properties": { - "color": { - "type": "string" - }, - "description": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "types.LabelOut": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/types.ItemSummary" - } - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LabelSummary": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LocationCount": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "itemCount": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LocationCreate": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "name": { - "type": "string" - } - } - }, - "types.LocationOut": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/definitions/types.ItemSummary" - } - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.LocationSummary": { - "type": "object", - "properties": { - "createdAt": { - "type": "string" - }, - "description": { - "type": "string" - }, - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "updatedAt": { - "type": "string" - } - } - }, - "types.TokenResponse": { + "v1.TokenResponse": { "type": "object", "properties": { "expiresAt": { @@ -1553,65 +1554,6 @@ "type": "string" } } - }, - "types.UserIn": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "name": { - "type": "string" - }, - "password": { - "type": "string" - } - } - }, - "types.UserOut": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "groupId": { - "type": "string" - }, - "groupName": { - "type": "string" - }, - "id": { - "type": "string" - }, - "isSuperuser": { - "type": "boolean" - }, - "name": { - "type": "string" - } - } - }, - "types.UserRegistration": { - "type": "object", - "properties": { - "groupName": { - "type": "string" - }, - "user": { - "$ref": "#/definitions/types.UserIn" - } - } - }, - "types.UserUpdate": { - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "name": { - "type": "string" - } - } } }, "securityDefinitions": { diff --git a/backend/app/api/docs/swagger.yaml b/backend/app/api/docs/swagger.yaml index 1749031..404d682 100644 --- a/backend/app/api/docs/swagger.yaml +++ b/backend/app/api/docs/swagger.yaml @@ -1,5 +1,302 @@ basePath: /api definitions: + repo.DocumentOut: + properties: + id: + type: string + path: + type: string + title: + type: string + type: object + repo.ItemAttachment: + properties: + createdAt: + type: string + document: + $ref: '#/definitions/repo.DocumentOut' + id: + type: string + type: + type: string + updatedAt: + type: string + type: object + repo.ItemAttachmentUpdate: + properties: + title: + type: string + type: + type: string + type: object + repo.ItemCreate: + properties: + description: + type: string + labelIds: + items: + type: string + type: array + locationId: + description: Edges + type: string + name: + type: string + type: object + repo.ItemOut: + properties: + attachments: + items: + $ref: '#/definitions/repo.ItemAttachment' + type: array + createdAt: + type: string + description: + type: string + id: + type: string + insured: + type: boolean + labels: + items: + $ref: '#/definitions/repo.LabelSummary' + type: array + lifetimeWarranty: + description: Warranty + type: boolean + location: + $ref: '#/definitions/repo.LocationSummary' + description: Edges + manufacturer: + type: string + modelNumber: + type: string + name: + type: string + notes: + description: Extras + type: string + purchaseFrom: + type: string + purchasePrice: + example: "0" + type: string + purchaseTime: + description: Purchase + type: string + quantity: + type: integer + serialNumber: + type: string + soldNotes: + type: string + soldPrice: + example: "0" + type: string + soldTime: + description: Sold + type: string + soldTo: + type: string + updatedAt: + type: string + warrantyDetails: + type: string + warrantyExpires: + type: string + type: object + repo.ItemSummary: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + insured: + type: boolean + labels: + items: + $ref: '#/definitions/repo.LabelSummary' + type: array + location: + $ref: '#/definitions/repo.LocationSummary' + description: Edges + name: + type: string + quantity: + type: integer + updatedAt: + type: string + type: object + repo.ItemUpdate: + properties: + description: + type: string + id: + type: string + insured: + type: boolean + labelIds: + items: + type: string + type: array + lifetimeWarranty: + description: Warranty + type: boolean + locationId: + description: Edges + type: string + manufacturer: + type: string + modelNumber: + type: string + name: + type: string + notes: + description: Extras + type: string + purchaseFrom: + type: string + purchasePrice: + example: "0" + type: string + purchaseTime: + description: Purchase + type: string + quantity: + type: integer + serialNumber: + description: Identifications + type: string + soldNotes: + type: string + soldPrice: + example: "0" + type: string + soldTime: + description: Sold + type: string + soldTo: + type: string + warrantyDetails: + type: string + warrantyExpires: + type: string + type: object + repo.LabelCreate: + properties: + color: + type: string + description: + type: string + name: + type: string + type: object + repo.LabelOut: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + items: + items: + $ref: '#/definitions/repo.ItemSummary' + type: array + name: + type: string + updatedAt: + type: string + type: object + repo.LabelSummary: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + name: + type: string + updatedAt: + type: string + type: object + repo.LocationCreate: + properties: + description: + type: string + name: + type: string + type: object + repo.LocationOut: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + items: + items: + $ref: '#/definitions/repo.ItemSummary' + type: array + name: + type: string + updatedAt: + type: string + type: object + repo.LocationOutCount: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + itemCount: + type: integer + name: + type: string + updatedAt: + type: string + type: object + repo.LocationSummary: + properties: + createdAt: + type: string + description: + type: string + id: + type: string + name: + type: string + updatedAt: + type: string + type: object + repo.UserOut: + properties: + email: + type: string + groupId: + type: string + groupName: + type: string + id: + type: string + isSuperuser: + type: boolean + name: + type: string + type: object + repo.UserUpdate: + properties: + email: + type: string + name: + type: string + type: object server.Result: properties: details: {} @@ -21,10 +318,21 @@ definitions: reason: type: string type: object - types.ApiSummary: + services.UserRegistration: + properties: + email: + type: string + groupName: + type: string + name: + type: string + password: + type: string + type: object + v1.ApiSummary: properties: build: - $ref: '#/definitions/types.Build' + $ref: '#/definitions/v1.Build' health: type: boolean message: @@ -36,7 +344,7 @@ definitions: type: string type: array type: object - types.Build: + v1.Build: properties: buildTime: type: string @@ -45,367 +353,18 @@ definitions: version: type: string type: object - types.DocumentOut: - properties: - id: - type: string - path: - type: string - title: - type: string - type: object - types.ItemAttachment: - properties: - createdAt: - type: string - document: - $ref: '#/definitions/types.DocumentOut' - id: - type: string - type: - type: string - updatedAt: - type: string - type: object - types.ItemAttachmentToken: + v1.ItemAttachmentToken: properties: token: type: string type: object - types.ItemAttachmentUpdate: - properties: - title: - type: string - type: - type: string - type: object - types.ItemCreate: - properties: - description: - type: string - labelIds: - items: - type: string - type: array - locationId: - description: Edges - type: string - name: - type: string - type: object - types.ItemOut: - properties: - attachments: - items: - $ref: '#/definitions/types.ItemAttachment' - type: array - createdAt: - type: string - description: - type: string - id: - type: string - insured: - type: boolean - labels: - items: - $ref: '#/definitions/types.LabelSummary' - type: array - lifetimeWarranty: - description: Warranty - type: boolean - location: - $ref: '#/definitions/types.LocationSummary' - description: Edges - manufacturer: - type: string - modelNumber: - type: string - name: - type: string - notes: - description: Extras - type: string - purchaseFrom: - type: string - purchasePrice: - example: "0" - type: string - purchaseTime: - description: Purchase - type: string - quantity: - type: integer - serialNumber: - description: Identifications - type: string - soldNotes: - type: string - soldPrice: - example: "0" - type: string - soldTime: - description: Sold - type: string - soldTo: - type: string - updatedAt: - type: string - warrantyDetails: - type: string - warrantyExpires: - type: string - type: object - types.ItemSummary: - properties: - createdAt: - type: string - description: - type: string - id: - type: string - insured: - type: boolean - labels: - items: - $ref: '#/definitions/types.LabelSummary' - type: array - lifetimeWarranty: - description: Warranty - type: boolean - location: - $ref: '#/definitions/types.LocationSummary' - description: Edges - manufacturer: - type: string - modelNumber: - type: string - name: - type: string - notes: - description: Extras - type: string - purchaseFrom: - type: string - purchasePrice: - example: "0" - type: string - purchaseTime: - description: Purchase - type: string - quantity: - type: integer - serialNumber: - description: Identifications - type: string - soldNotes: - type: string - soldPrice: - example: "0" - type: string - soldTime: - description: Sold - type: string - soldTo: - type: string - updatedAt: - type: string - warrantyDetails: - type: string - warrantyExpires: - type: string - type: object - types.ItemUpdate: - properties: - description: - type: string - id: - type: string - insured: - type: boolean - labelIds: - items: - type: string - type: array - lifetimeWarranty: - description: Warranty - type: boolean - locationId: - description: Edges - type: string - manufacturer: - type: string - modelNumber: - type: string - name: - type: string - notes: - description: Extras - type: string - purchaseFrom: - type: string - purchasePrice: - example: "0" - type: string - purchaseTime: - description: Purchase - type: string - quantity: - type: integer - serialNumber: - description: Identifications - type: string - soldNotes: - type: string - soldPrice: - example: "0" - type: string - soldTime: - description: Sold - type: string - soldTo: - type: string - warrantyDetails: - type: string - warrantyExpires: - type: string - type: object - types.LabelCreate: - properties: - color: - type: string - description: - type: string - name: - type: string - type: object - types.LabelOut: - properties: - createdAt: - type: string - description: - type: string - id: - type: string - items: - items: - $ref: '#/definitions/types.ItemSummary' - type: array - name: - type: string - updatedAt: - type: string - type: object - types.LabelSummary: - properties: - createdAt: - type: string - description: - type: string - id: - type: string - name: - type: string - updatedAt: - type: string - type: object - types.LocationCount: - properties: - createdAt: - type: string - description: - type: string - id: - type: string - itemCount: - type: integer - name: - type: string - updatedAt: - type: string - type: object - types.LocationCreate: - properties: - description: - type: string - name: - type: string - type: object - types.LocationOut: - properties: - createdAt: - type: string - description: - type: string - id: - type: string - items: - items: - $ref: '#/definitions/types.ItemSummary' - type: array - name: - type: string - updatedAt: - type: string - type: object - types.LocationSummary: - properties: - createdAt: - type: string - description: - type: string - id: - type: string - name: - type: string - updatedAt: - type: string - type: object - types.TokenResponse: + v1.TokenResponse: properties: expiresAt: type: string token: type: string type: object - types.UserIn: - properties: - email: - type: string - name: - type: string - password: - type: string - type: object - types.UserOut: - properties: - email: - type: string - groupId: - type: string - groupName: - type: string - id: - type: string - isSuperuser: - type: boolean - name: - type: string - type: object - types.UserRegistration: - properties: - groupName: - type: string - user: - $ref: '#/definitions/types.UserIn' - type: object - types.UserUpdate: - properties: - email: - type: string - name: - type: string - type: object info: contact: name: Don't @@ -430,7 +389,7 @@ paths: - properties: items: items: - $ref: '#/definitions/types.ItemSummary' + $ref: '#/definitions/repo.ItemSummary' type: array type: object security: @@ -445,14 +404,14 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.ItemCreate' + $ref: '#/definitions/repo.ItemCreate' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/types.ItemSummary' + $ref: '#/definitions/repo.ItemSummary' security: - Bearer: [] summary: Create a new item @@ -489,7 +448,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.ItemOut' + $ref: '#/definitions/repo.ItemOut' security: - Bearer: [] summary: Gets a item and fields @@ -507,14 +466,14 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.ItemUpdate' + $ref: '#/definitions/repo.ItemUpdate' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/types.ItemOut' + $ref: '#/definitions/repo.ItemOut' security: - Bearer: [] summary: updates a item @@ -549,7 +508,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.ItemOut' + $ref: '#/definitions/repo.ItemOut' "422": description: Unprocessable Entity schema: @@ -600,7 +559,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.ItemAttachmentToken' + $ref: '#/definitions/v1.ItemAttachmentToken' security: - Bearer: [] summary: retrieves an attachment for an item @@ -623,12 +582,12 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.ItemAttachmentUpdate' + $ref: '#/definitions/repo.ItemAttachmentUpdate' responses: "200": description: OK schema: - $ref: '#/definitions/types.ItemOut' + $ref: '#/definitions/repo.ItemOut' security: - Bearer: [] summary: retrieves an attachment for an item @@ -688,7 +647,7 @@ paths: - properties: items: items: - $ref: '#/definitions/types.LabelOut' + $ref: '#/definitions/repo.LabelOut' type: array type: object security: @@ -703,14 +662,14 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.LabelCreate' + $ref: '#/definitions/repo.LabelCreate' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/types.LabelSummary' + $ref: '#/definitions/repo.LabelSummary' security: - Bearer: [] summary: Create a new label @@ -747,7 +706,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.LabelOut' + $ref: '#/definitions/repo.LabelOut' security: - Bearer: [] summary: Gets a label and fields @@ -766,7 +725,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.LabelOut' + $ref: '#/definitions/repo.LabelOut' security: - Bearer: [] summary: updates a label @@ -785,7 +744,7 @@ paths: - properties: items: items: - $ref: '#/definitions/types.LocationCount' + $ref: '#/definitions/repo.LocationOutCount' type: array type: object security: @@ -800,14 +759,14 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.LocationCreate' + $ref: '#/definitions/repo.LocationCreate' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/types.LocationSummary' + $ref: '#/definitions/repo.LocationSummary' security: - Bearer: [] summary: Create a new location @@ -844,7 +803,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.LocationOut' + $ref: '#/definitions/repo.LocationOut' security: - Bearer: [] summary: Gets a location and fields @@ -863,7 +822,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.LocationOut' + $ref: '#/definitions/repo.LocationOut' security: - Bearer: [] summary: updates a location @@ -877,7 +836,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.ApiSummary' + $ref: '#/definitions/v1.ApiSummary' summary: Retrieves the basic information about the API tags: - Base @@ -903,7 +862,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/types.TokenResponse' + $ref: '#/definitions/v1.TokenResponse' summary: User Login tags: - Authentication @@ -938,7 +897,7 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.UserRegistration' + $ref: '#/definitions/services.UserRegistration' produces: - application/json responses: @@ -970,7 +929,7 @@ paths: - $ref: '#/definitions/server.Result' - properties: item: - $ref: '#/definitions/types.UserOut' + $ref: '#/definitions/repo.UserOut' type: object security: - Bearer: [] @@ -984,7 +943,7 @@ paths: name: payload required: true schema: - $ref: '#/definitions/types.UserUpdate' + $ref: '#/definitions/repo.UserUpdate' produces: - application/json responses: @@ -995,7 +954,7 @@ paths: - $ref: '#/definitions/server.Result' - properties: item: - $ref: '#/definitions/types.UserUpdate' + $ref: '#/definitions/repo.UserUpdate' type: object security: - Bearer: [] diff --git a/backend/app/api/main.go b/backend/app/api/main.go index 5d233e7..bd833b3 100644 --- a/backend/app/api/main.go +++ b/backend/app/api/main.go @@ -81,8 +81,8 @@ func run(cfg *config.Config) error { } app.db = c - app.repos = repo.EntAllRepos(c) - app.services = services.NewServices(app.repos, cfg.Storage.Data) + app.repos = repo.EntAllRepos(c, cfg.Storage.Data) + app.services = services.NewServices(app.repos) // ========================================================================= // Start Server diff --git a/backend/app/api/middleware.go b/backend/app/api/middleware.go index 53f70bb..ed92ff5 100644 --- a/backend/app/api/middleware.go +++ b/backend/app/api/middleware.go @@ -57,7 +57,7 @@ func (a *app) mwAuthToken(next http.Handler) http.Handler { return } - r = r.WithContext(services.SetUserCtx(r.Context(), usr, requestToken)) + r = r.WithContext(services.SetUserCtx(r.Context(), &usr, requestToken)) next.ServeHTTP(w, r) }) diff --git a/backend/app/api/routes.go b/backend/app/api/routes.go index 1ed1668..4b20fe8 100644 --- a/backend/app/api/routes.go +++ b/backend/app/api/routes.go @@ -14,7 +14,6 @@ import ( _ "github.com/hay-kot/homebox/backend/app/api/docs" v1 "github.com/hay-kot/homebox/backend/app/api/v1" "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/rs/zerolog/log" httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware ) @@ -45,7 +44,7 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux { v1Base := v1.BaseUrlFunc(prefix) v1Ctrl := v1.NewControllerV1(a.services, v1.WithMaxUploadSize(a.conf.Web.MaxUploadSize)) { - r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, types.Build{ + r.Get(v1Base("/status"), v1Ctrl.HandleBase(func() bool { return true }, v1.Build{ Version: Version, Commit: Commit, BuildTime: BuildTime, diff --git a/backend/app/api/v1/controller.go b/backend/app/api/v1/controller.go index ac135ef..c3e7b4b 100644 --- a/backend/app/api/v1/controller.go +++ b/backend/app/api/v1/controller.go @@ -4,7 +4,6 @@ import ( "net/http" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" ) @@ -19,6 +18,22 @@ type V1Controller struct { maxUploadSize int64 } +type ( + Build struct { + Version string `json:"version"` + Commit string `json:"commit"` + BuildTime string `json:"buildTime"` + } + + ApiSummary struct { + Healthy bool `json:"health"` + Versions []string `json:"versions"` + Title string `json:"title"` + Message string `json:"message"` + Build Build + } +) + func BaseUrlFunc(prefix string) func(s string) string { v1Base := prefix + "/v1" prefixFunc := func(s string) string { @@ -42,11 +57,11 @@ type ReadyFunc func() bool // @Summary Retrieves the basic information about the API // @Tags Base // @Produce json -// @Success 200 {object} types.ApiSummary +// @Success 200 {object} ApiSummary // @Router /v1/status [GET] -func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build types.Build) http.HandlerFunc { +func (ctrl *V1Controller) HandleBase(ready ReadyFunc, build Build) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - server.Respond(w, http.StatusOK, types.ApiSummary{ + server.Respond(w, http.StatusOK, ApiSummary{ Healthy: ready(), Title: "Go API Template", Message: "Welcome to the Go API Template Application!", diff --git a/backend/app/api/v1/controller_test.go b/backend/app/api/v1/controller_test.go deleted file mode 100644 index 6f555bb..0000000 --- a/backend/app/api/v1/controller_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package v1 - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/hay-kot/homebox/backend/internal/types" - "github.com/stretchr/testify/assert" -) - -func Test_NewHandlerV1(t *testing.T) { - - v1Base := BaseUrlFunc("/testing/v1") - ctrl := NewControllerV1(mockHandler.svc) - - assert.NotNil(t, ctrl) - - assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123")) - assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123")) -} - -func TestHandlersv1_HandleBase(t *testing.T) { - // Setup - hdlrFunc := mockHandler.HandleBase(func() bool { return true }, types.Build{ - Version: "0.1.0", - Commit: "HEAD", - BuildTime: "now", - }) - - // Call Handler Func - rr := httptest.NewRecorder() - hdlrFunc(rr, nil) - - // Validate Status Code - if rr.Code != http.StatusOK { - t.Errorf("Expected status code to be %d, got %d", http.StatusOK, rr.Code) - } - - // Validate Json Payload - expected := `{"health":true,"versions":null,"title":"Go API Template","message":"Welcome to the Go API Template Application!","Build":{"version":"0.1.0","commit":"HEAD","buildTime":"now"}}` - - if rr.Body.String() != expected { - t.Errorf("Expected json to be %s, got %s", expected, rr.Body.String()) - } - -} diff --git a/backend/app/api/v1/main_test.go b/backend/app/api/v1/main_test.go deleted file mode 100644 index ee08ccd..0000000 --- a/backend/app/api/v1/main_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package v1 - -import ( - "context" - "testing" - - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/mocks" - "github.com/hay-kot/homebox/backend/internal/mocks/factories" - "github.com/hay-kot/homebox/backend/internal/types" -) - -var mockHandler = &V1Controller{} -var users = []*ent.User{} - -func userPool() func() { - create := []types.UserCreate{ - factories.UserFactory(), - factories.UserFactory(), - factories.UserFactory(), - factories.UserFactory(), - } - - userOut := []*ent.User{} - - for _, user := range create { - usrOut, _ := mockHandler.svc.Admin.Create(context.Background(), user) - userOut = append(userOut, usrOut) - } - - users = userOut - - purge := func() { - _ = mockHandler.svc.Admin.DeleteAll(context.Background()) - } - - return purge -} - -func TestMain(m *testing.M) { - // Set Handler Vars - repos, closeDb := mocks.GetEntRepos() - mockHandler.svc = mocks.GetMockServices(repos) - - defer func() { - _ = closeDb() - }() - - purge := userPool() - defer purge() - - m.Run() -} diff --git a/backend/app/api/v1/partials.go b/backend/app/api/v1/partials.go index 195ce8b..f655ed1 100644 --- a/backend/app/api/v1/partials.go +++ b/backend/app/api/v1/partials.go @@ -5,8 +5,8 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) @@ -21,12 +21,12 @@ and makes it a little more consistent when error handling and logging. // from the context. If either of these fail, it will return an error. When an error // occurs it will also write the error to the response. As such, if an error is returned // from this function you can return immediately without writing to the response. -func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *types.UserOut, error) { +func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *repo.UserOut, error) { uid, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { log.Err(err).Msg("failed to parse id") server.RespondError(w, http.StatusBadRequest, err) - return uuid.Nil, nil, err + return uuid.Nil, &repo.UserOut{}, err } user := services.UseUserCtx(r.Context()) diff --git a/backend/app/api/v1/v1_ctrl_auth.go b/backend/app/api/v1/v1_ctrl_auth.go index 2a44664..253dee2 100644 --- a/backend/app/api/v1/v1_ctrl_auth.go +++ b/backend/app/api/v1/v1_ctrl_auth.go @@ -3,13 +3,25 @@ package v1 import ( "errors" "net/http" + "time" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) +type ( + TokenResponse struct { + Token string `json:"token"` + ExpiresAt time.Time `json:"expiresAt"` + } + + LoginForm struct { + Username string `json:"username"` + Password string `json:"password"` + } +) + // HandleAuthLogin godoc // @Summary User Login // @Tags Authentication @@ -18,11 +30,11 @@ import ( // @Param username formData string false "string" example(admin@admin.com) // @Param password formData string false "string" example(admin) // @Produce json -// @Success 200 {object} types.TokenResponse +// @Success 200 {object} TokenResponse // @Router /v1/users/login [POST] func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - loginForm := &types.LoginForm{} + loginForm := &LoginForm{} if r.Header.Get("Content-Type") == server.ContentFormUrlEncoded { err := r.ParseForm() @@ -59,9 +71,9 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc { return } - server.Respond(w, http.StatusOK, types.TokenResponse{ - BearerToken: "Bearer " + newToken.Raw, - ExpiresAt: newToken.ExpiresAt, + server.Respond(w, http.StatusOK, TokenResponse{ + Token: "Bearer " + newToken.Raw, + ExpiresAt: newToken.ExpiresAt, }) } } diff --git a/backend/app/api/v1/v1_ctrl_items.go b/backend/app/api/v1/v1_ctrl_items.go index 7de278e..24502b7 100644 --- a/backend/app/api/v1/v1_ctrl_items.go +++ b/backend/app/api/v1/v1_ctrl_items.go @@ -4,8 +4,8 @@ import ( "encoding/csv" "net/http" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) @@ -14,7 +14,7 @@ import ( // @Summary Get All Items // @Tags Items // @Produce json -// @Success 200 {object} server.Results{items=[]types.ItemSummary} +// @Success 200 {object} server.Results{items=[]repo.ItemSummary} // @Router /v1/items [GET] // @Security Bearer func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc { @@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc { // @Summary Create a new item // @Tags Items // @Produce json -// @Param payload body types.ItemCreate true "Item Data" -// @Success 200 {object} types.ItemSummary +// @Param payload body repo.ItemCreate true "Item Data" +// @Success 200 {object} repo.ItemSummary // @Router /v1/items [POST] // @Security Bearer func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - createData := types.ItemCreate{} + createData := repo.ItemCreate{} if err := server.Decode(r, &createData); err != nil { log.Err(err).Msg("failed to decode request body") server.RespondError(w, http.StatusInternalServerError, err) @@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc { // @Tags Items // @Produce json // @Param id path string true "Item ID" -// @Success 200 {object} types.ItemOut +// @Success 200 {object} repo.ItemOut // @Router /v1/items/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { @@ -114,14 +114,14 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc { // @Summary updates a item // @Tags Items // @Produce json -// @Param id path string true "Item ID" -// @Param payload body types.ItemUpdate true "Item Data" -// @Success 200 {object} types.ItemOut +// @Param id path string true "Item ID" +// @Param payload body repo.ItemUpdate true "Item Data" +// @Success 200 {object} repo.ItemOut // @Router /v1/items/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - body := types.ItemUpdate{} + body := repo.ItemUpdate{} if err := server.Decode(r, &body); err != nil { log.Err(err).Msg("failed to decode request body") server.RespondError(w, http.StatusInternalServerError, err) diff --git a/backend/app/api/v1/v1_ctrl_items_attachments.go b/backend/app/api/v1/v1_ctrl_items_attachments.go index b373ead..c8ff194 100644 --- a/backend/app/api/v1/v1_ctrl_items_attachments.go +++ b/backend/app/api/v1/v1_ctrl_items_attachments.go @@ -4,17 +4,22 @@ import ( "errors" "fmt" "net/http" - "path/filepath" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent/attachment" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) +type ( + ItemAttachmentToken struct { + Token string `json:"token"` + } +) + // HandleItemsImport godocs // @Summary imports items into the database // @Tags Items @@ -23,7 +28,7 @@ import ( // @Param file formData file true "File attachment" // @Param type formData string true "Type of file" // @Param name formData string true "name of the file including extension" -// @Success 200 {object} types.ItemOut +// @Success 200 {object} repo.ItemOut // @Failure 422 {object} []server.ValidationError // @Router /v1/items/{id}/attachments [POST] // @Security Bearer @@ -105,7 +110,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := server.GetParam(r, "token", "") - path, err := ctrl.svc.Items.AttachmentPath(r.Context(), token) + doc, err := ctrl.svc.Items.AttachmentPath(r.Context(), token) if err != nil { log.Err(err).Msg("failed to get attachment") @@ -113,9 +118,9 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc { return } - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filepath.Base(path))) + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", doc.Title)) w.Header().Set("Content-Type", "application/octet-stream") - http.ServeFile(w, r, path) + http.ServeFile(w, r, doc.Path) } } @@ -125,7 +130,7 @@ func (ctrl *V1Controller) HandleItemAttachmentDownload() http.HandlerFunc { // @Produce application/octet-stream // @Param id path string true "Item ID" // @Param attachment_id path string true "Attachment ID" -// @Success 200 {object} types.ItemAttachmentToken +// @Success 200 {object} ItemAttachmentToken // @Router /v1/items/{id}/attachments/{attachment_id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleItemAttachmentToken() http.HandlerFunc { @@ -147,10 +152,10 @@ func (ctrl *V1Controller) HandleItemAttachmentDelete() http.HandlerFunc { // HandleItemAttachmentUpdate godocs // @Summary retrieves an attachment for an item // @Tags Items -// @Param id path string true "Item ID" -// @Param attachment_id path string true "Attachment ID" -// @Param payload body types.ItemAttachmentUpdate true "Attachment Update" -// @Success 200 {object} types.ItemOut +// @Param id path string true "Item ID" +// @Param attachment_id path string true "Attachment ID" +// @Param payload body repo.ItemAttachmentUpdate true "Attachment Update" +// @Success 200 {object} repo.ItemOut // @Router /v1/items/{id}/attachments/{attachment_id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleItemAttachmentUpdate() http.HandlerFunc { @@ -201,7 +206,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r } } - server.Respond(w, http.StatusOK, types.ItemAttachmentToken{Token: token}) + server.Respond(w, http.StatusOK, ItemAttachmentToken{Token: token}) // Delete Attachment Handler case http.MethodDelete: @@ -216,7 +221,7 @@ func (ctrl *V1Controller) handleItemAttachmentsHandler(w http.ResponseWriter, r // Update Attachment Handler case http.MethodPut: - var attachment types.ItemAttachmentUpdate + var attachment repo.ItemAttachmentUpdate err = server.Decode(r, &attachment) if err != nil { log.Err(err).Msg("failed to decode attachment") diff --git a/backend/app/api/v1/v1_ctrl_labels.go b/backend/app/api/v1/v1_ctrl_labels.go index 38bd666..f4811e3 100644 --- a/backend/app/api/v1/v1_ctrl_labels.go +++ b/backend/app/api/v1/v1_ctrl_labels.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/hay-kot/homebox/backend/ent" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) @@ -14,7 +14,7 @@ import ( // @Summary Get All Labels // @Tags Labels // @Produce json -// @Success 200 {object} server.Results{items=[]types.LabelOut} +// @Success 200 {object} server.Results{items=[]repo.LabelOut} // @Router /v1/labels [GET] // @Security Bearer func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc { @@ -34,13 +34,13 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc { // @Summary Create a new label // @Tags Labels // @Produce json -// @Param payload body types.LabelCreate true "Label Data" -// @Success 200 {object} types.LabelSummary +// @Param payload body repo.LabelCreate true "Label Data" +// @Success 200 {object} repo.LabelSummary // @Router /v1/labels [POST] // @Security Bearer func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - createData := types.LabelCreate{} + createData := repo.LabelCreate{} if err := server.Decode(r, &createData); err != nil { log.Err(err).Msg("error decoding label create data") server.RespondError(w, http.StatusInternalServerError, err) @@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc { // @Tags Labels // @Produce json // @Param id path string true "Label ID" -// @Success 200 {object} types.LabelOut +// @Success 200 {object} repo.LabelOut // @Router /v1/labels/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc { @@ -122,12 +122,12 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc { // @Tags Labels // @Produce json // @Param id path string true "Label ID" -// @Success 200 {object} types.LabelOut +// @Success 200 {object} repo.LabelOut // @Router /v1/labels/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - body := types.LabelUpdate{} + body := repo.LabelUpdate{} if err := server.Decode(r, &body); err != nil { log.Err(err).Msg("error decoding label update data") server.RespondError(w, http.StatusInternalServerError, err) diff --git a/backend/app/api/v1/v1_ctrl_locations.go b/backend/app/api/v1/v1_ctrl_locations.go index 6126d3d..5dabc00 100644 --- a/backend/app/api/v1/v1_ctrl_locations.go +++ b/backend/app/api/v1/v1_ctrl_locations.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/hay-kot/homebox/backend/ent" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) @@ -14,7 +14,7 @@ import ( // @Summary Get All Locations // @Tags Locations // @Produce json -// @Success 200 {object} server.Results{items=[]types.LocationCount} +// @Success 200 {object} server.Results{items=[]repo.LocationOutCount} // @Router /v1/locations [GET] // @Security Bearer func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc { @@ -35,13 +35,13 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc { // @Summary Create a new location // @Tags Locations // @Produce json -// @Param payload body types.LocationCreate true "Location Data" -// @Success 200 {object} types.LocationSummary +// @Param payload body repo.LocationCreate true "Location Data" +// @Success 200 {object} repo.LocationSummary // @Router /v1/locations [POST] // @Security Bearer func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - createData := types.LocationCreate{} + createData := repo.LocationCreate{} if err := server.Decode(r, &createData); err != nil { log.Err(err).Msg("failed to decode location create data") server.RespondError(w, http.StatusInternalServerError, err) @@ -90,7 +90,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc { // @Tags Locations // @Produce json // @Param id path string true "Location ID" -// @Success 200 {object} types.LocationOut +// @Success 200 {object} repo.LocationOut // @Router /v1/locations/{id} [GET] // @Security Bearer func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { @@ -105,12 +105,16 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { if ent.IsNotFound(err) { log.Err(err). Str("id", uid.String()). + Str("gid", user.GroupID.String()). Msg("location not found") server.RespondError(w, http.StatusNotFound, err) return } - log.Err(err).Msg("failed to get location") + log.Err(err). + Str("id", uid.String()). + Str("gid", user.GroupID.String()). + Msg("failed to get location") server.RespondServerError(w) return } @@ -123,12 +127,12 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc { // @Tags Locations // @Produce json // @Param id path string true "Location ID" -// @Success 200 {object} types.LocationOut +// @Success 200 {object} repo.LocationOut // @Router /v1/locations/{id} [PUT] // @Security Bearer func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - body := types.LocationUpdate{} + body := repo.LocationUpdate{} if err := server.Decode(r, &body); err != nil { log.Err(err).Msg("failed to decode location update data") server.RespondError(w, http.StatusInternalServerError, err) diff --git a/backend/app/api/v1/v1_ctrl_user.go b/backend/app/api/v1/v1_ctrl_user.go index 0a4b7c7..eba2219 100644 --- a/backend/app/api/v1/v1_ctrl_user.go +++ b/backend/app/api/v1/v1_ctrl_user.go @@ -4,8 +4,8 @@ import ( "net/http" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/internal/services" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/server" "github.com/rs/zerolog/log" ) @@ -14,12 +14,12 @@ import ( // @Summary Get the current user // @Tags User // @Produce json -// @Param payload body types.UserRegistration true "User Data" +// @Param payload body services.UserRegistration true "User Data" // @Success 204 // @Router /v1/users/register [Post] func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - regData := types.UserRegistration{} + regData := services.UserRegistration{} if err := server.Decode(r, ®Data); err != nil { log.Err(err).Msg("failed to decode user registration data") @@ -29,6 +29,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc { _, err := ctrl.svc.User.RegisterUser(r.Context(), regData) if err != nil { + log.Err(err).Msg("failed to register user") server.RespondError(w, http.StatusInternalServerError, err) return } @@ -41,7 +42,7 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc { // @Summary Get the current user // @Tags User // @Produce json -// @Success 200 {object} server.Result{item=types.UserOut} +// @Success 200 {object} server.Result{item=repo.UserOut} // @Router /v1/users/self [GET] // @Security Bearer func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc { @@ -62,13 +63,13 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc { // @Summary Update the current user // @Tags User // @Produce json -// @Param payload body types.UserUpdate true "User Data" -// @Success 200 {object} server.Result{item=types.UserUpdate} +// @Param payload body repo.UserUpdate true "User Data" +// @Success 200 {object} server.Result{item=repo.UserUpdate} // @Router /v1/users/self [PUT] // @Security Bearer func (ctrl *V1Controller) HandleUserSelfUpdate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - updateData := types.UserUpdate{} + updateData := repo.UserUpdate{} if err := server.Decode(r, &updateData); err != nil { log.Err(err).Msg("failed to decode user update data") server.RespondError(w, http.StatusBadRequest, err) diff --git a/backend/internal/mocks/factories/users.go b/backend/internal/mocks/factories/users.go index 4f9389d..6f618fb 100644 --- a/backend/internal/mocks/factories/users.go +++ b/backend/internal/mocks/factories/users.go @@ -1,13 +1,13 @@ package factories import ( - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/pkgs/faker" ) -func UserFactory() types.UserCreate { +func UserFactory() repo.UserCreate { f := faker.NewFaker() - return types.UserCreate{ + return repo.UserCreate{ Name: f.Str(10), Email: f.Email(), Password: f.Str(10), diff --git a/backend/internal/mocks/mocker_services.go b/backend/internal/mocks/mocker_services.go deleted file mode 100644 index d1e80bd..0000000 --- a/backend/internal/mocks/mocker_services.go +++ /dev/null @@ -1,10 +0,0 @@ -package mocks - -import ( - "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/services" -) - -func GetMockServices(repos *repo.AllRepos) *services.AllServices { - return services.NewServices(repos, "/tmp/homebox") -} diff --git a/backend/internal/mocks/mocks_ent_repo.go b/backend/internal/mocks/mocks_ent_repo.go deleted file mode 100644 index 4b7abcc..0000000 --- a/backend/internal/mocks/mocks_ent_repo.go +++ /dev/null @@ -1,22 +0,0 @@ -package mocks - -import ( - "context" - - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/repo" - _ "github.com/mattn/go-sqlite3" -) - -func GetEntRepos() (*repo.AllRepos, func() error) { - c, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") - if err != nil { - panic(err) - } - - if err := c.Schema.Create(context.Background()); err != nil { - panic(err) - } - - return repo.EntAllRepos(c), c.Close -} diff --git a/backend/internal/repo/id_set.go b/backend/internal/repo/id_set.go index 0041d93..a39ac40 100644 --- a/backend/internal/repo/id_set.go +++ b/backend/internal/repo/id_set.go @@ -1,6 +1,9 @@ package repo -import "github.com/google/uuid" +import ( + "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/pkgs/set" +) // HasID is an interface to entities that have an ID uuid.UUID field and a GetID() method. // This interface is fulfilled by all entities generated by entgo.io/ent via a custom template @@ -8,55 +11,11 @@ type HasID interface { GetID() uuid.UUID } -// IDSet is a utility set-like type for working with sets of uuid.UUIDs within a repository -// instance. Most useful for comparing lists of UUIDs for processing relationship -// IDs and remove/adding relationships as required. -// -// # See how ItemRepo uses it to manage the Labels-To-Items relationship -// -// NOTE: may be worth moving this to a more generic package/set implementation -// or use a 3rd party set library, but this is good enough for now -type IDSet struct { - mp map[uuid.UUID]struct{} -} - -func NewIDSet(l int) *IDSet { - return &IDSet{ - mp: make(map[uuid.UUID]struct{}, l), - } -} - -func EntitiesToIDSet[T HasID](entities []T) *IDSet { - s := NewIDSet(len(entities)) +func newIDSet[T HasID](entities []T) set.Set[uuid.UUID] { + uuids := make([]uuid.UUID, 0, len(entities)) for _, e := range entities { - s.Add(e.GetID()) + uuids = append(uuids, e.GetID()) } - return s -} -func (t *IDSet) Slice() []uuid.UUID { - s := make([]uuid.UUID, 0, len(t.mp)) - for k := range t.mp { - s = append(s, k) - } - return s -} - -func (t *IDSet) Add(ids ...uuid.UUID) { - for _, id := range ids { - t.mp[id] = struct{}{} - } -} - -func (t *IDSet) Has(id uuid.UUID) bool { - _, ok := t.mp[id] - return ok -} - -func (t *IDSet) Len() int { - return len(t.mp) -} - -func (t *IDSet) Remove(id uuid.UUID) { - delete(t.mp, id) + return set.New(uuids...) } diff --git a/backend/internal/repo/main_test.go b/backend/internal/repo/main_test.go index 14bfbd8..aa864e5 100644 --- a/backend/internal/repo/main_test.go +++ b/backend/internal/repo/main_test.go @@ -18,7 +18,7 @@ var ( tClient *ent.Client tRepos *AllRepos - tUser *ent.User + tUser UserOut tGroup *ent.Group ) @@ -53,7 +53,7 @@ func TestMain(m *testing.M) { } tClient = client - tRepos = EntAllRepos(tClient) + tRepos = EntAllRepos(tClient, os.TempDir()) defer client.Close() bootstrap() diff --git a/backend/internal/repo/map_helpers.go b/backend/internal/repo/map_helpers.go new file mode 100644 index 0000000..a9c0bca --- /dev/null +++ b/backend/internal/repo/map_helpers.go @@ -0,0 +1,52 @@ +package repo + +// mapTErrFunc is a factory function that returns a mapper function that +// wraps the given mapper function but first will check for an error and +// return the error if present. +// +// Helpful for wrapping database calls that return both a value and an error +func mapTErrFunc[T any, Y any](fn func(T) Y) func(T, error) (Y, error) { + return func(t T, err error) (Y, error) { + if err != nil { + var zero Y + return zero, err + } + + return fn(t), nil + } +} + +// TODO: Future Usage +// func mapEachFunc[T any, Y any](fn func(T) Y) func([]T) []Y { +// return func(items []T) []Y { +// result := make([]Y, len(items)) +// for i, item := range items { +// result[i] = fn(item) +// } + +// return result +// } +// } + +func mapTEachErrFunc[T any, Y any](fn func(T) Y) func([]T, error) ([]Y, error) { + return func(items []T, err error) ([]Y, error) { + if err != nil { + return nil, err + } + + result := make([]Y, len(items)) + for i, item := range items { + result[i] = fn(item) + } + + return result, nil + } +} + +func mapEach[T any, U any](items []T, fn func(T) U) []U { + result := make([]U, len(items)) + for i, item := range items { + result[i] = fn(item) + } + return result +} diff --git a/backend/internal/repo/repo_documents_tokens.go b/backend/internal/repo/repo_document_tokens.go similarity index 54% rename from backend/internal/repo/repo_documents_tokens.go rename to backend/internal/repo/repo_document_tokens.go index 1924f1d..903240c 100644 --- a/backend/internal/repo/repo_documents_tokens.go +++ b/backend/internal/repo/repo_document_tokens.go @@ -7,7 +7,6 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/documenttoken" - "github.com/hay-kot/homebox/backend/internal/types" ) // DocumentTokensRepository is a repository for Document entity @@ -15,7 +14,35 @@ type DocumentTokensRepository struct { db *ent.Client } -func (r *DocumentTokensRepository) Create(ctx context.Context, data types.DocumentTokenCreate) (*ent.DocumentToken, error) { +type ( + DocumentToken struct { + ID uuid.UUID `json:"-"` + TokenHash []byte `json:"tokenHash"` + ExpiresAt time.Time `json:"expiresAt"` + DocumentID uuid.UUID `json:"documentId"` + } + + DocumentTokenCreate struct { + TokenHash []byte `json:"tokenHash"` + DocumentID uuid.UUID `json:"documentId"` + ExpiresAt time.Time `json:"expiresAt"` + } +) + +var ( + mapDocumentTokenErr = mapTErrFunc(mapDocumentToken) +) + +func mapDocumentToken(e *ent.DocumentToken) DocumentToken { + return DocumentToken{ + ID: e.ID, + TokenHash: e.Token, + ExpiresAt: e.ExpiresAt, + DocumentID: e.Edges.Document.ID, + } +} + +func (r *DocumentTokensRepository) Create(ctx context.Context, data DocumentTokenCreate) (DocumentToken, error) { result, err := r.db.DocumentToken.Create(). SetDocumentID(data.DocumentID). SetToken(data.TokenHash). @@ -23,13 +50,13 @@ func (r *DocumentTokensRepository) Create(ctx context.Context, data types.Docume Save(ctx) if err != nil { - return nil, err + return DocumentToken{}, err } - return r.db.DocumentToken.Query(). + return mapDocumentTokenErr(r.db.DocumentToken.Query(). Where(documenttoken.ID(result.ID)). WithDocument(). - Only(ctx) + Only(ctx)) } func (r *DocumentTokensRepository) PurgeExpiredTokens(ctx context.Context) (int, error) { diff --git a/backend/internal/repo/repo_documents_tokens_test.go b/backend/internal/repo/repo_document_tokens_test.go similarity index 84% rename from backend/internal/repo/repo_documents_tokens_test.go rename to backend/internal/repo/repo_document_tokens_test.go index ebd84ed..34f0a17 100644 --- a/backend/internal/repo/repo_documents_tokens_test.go +++ b/backend/internal/repo/repo_document_tokens_test.go @@ -8,7 +8,6 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/documenttoken" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/stretchr/testify/assert" ) @@ -19,7 +18,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) { type args struct { ctx context.Context - data types.DocumentTokenCreate + data DocumentTokenCreate } tests := []struct { name string @@ -31,7 +30,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) { name: "create document token", args: args{ ctx: context.Background(), - data: types.DocumentTokenCreate{ + data: DocumentTokenCreate{ DocumentID: doc.ID, TokenHash: []byte("token"), ExpiresAt: expires, @@ -39,7 +38,9 @@ func TestDocumentTokensRepository_Create(t *testing.T) { }, want: &ent.DocumentToken{ Edges: ent.DocumentTokenEdges{ - Document: doc, + Document: &ent.Document{ + ID: doc.ID, + }, }, Token: []byte("token"), ExpiresAt: expires, @@ -50,7 +51,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) { name: "create document token with empty token", args: args{ ctx: context.Background(), - data: types.DocumentTokenCreate{ + data: DocumentTokenCreate{ DocumentID: doc.ID, TokenHash: []byte(""), ExpiresAt: expires, @@ -63,7 +64,7 @@ func TestDocumentTokensRepository_Create(t *testing.T) { name: "create document token with empty document id", args: args{ ctx: context.Background(), - data: types.DocumentTokenCreate{ + data: DocumentTokenCreate{ DocumentID: uuid.Nil, TokenHash: []byte("token"), ExpiresAt: expires, @@ -94,18 +95,18 @@ func TestDocumentTokensRepository_Create(t *testing.T) { return } - assert.Equal(t, tt.want.Token, got.Token) + assert.Equal(t, tt.want.Token, got.TokenHash) assert.WithinDuration(t, tt.want.ExpiresAt, got.ExpiresAt, time.Duration(1)*time.Second) - assert.Equal(t, tt.want.Edges.Document.ID, got.Edges.Document.ID) + assert.Equal(t, tt.want.Edges.Document.ID, got.DocumentID) }) } } -func useDocTokens(t *testing.T, num int) []*ent.DocumentToken { +func useDocTokens(t *testing.T, num int) []DocumentToken { entity := useDocs(t, 1)[0] - results := make([]*ent.DocumentToken, 0, num) + results := make([]DocumentToken, 0, num) ids := make([]uuid.UUID, 0, num) t.Cleanup(func() { @@ -115,7 +116,7 @@ func useDocTokens(t *testing.T, num int) []*ent.DocumentToken { }) for i := 0; i < num; i++ { - e, err := tRepos.DocTokens.Create(context.Background(), types.DocumentTokenCreate{ + e, err := tRepos.DocTokens.Create(context.Background(), DocumentTokenCreate{ DocumentID: entity.ID, TokenHash: []byte(fk.Str(10)), ExpiresAt: fk.Time(), diff --git a/backend/internal/repo/repo_documents.go b/backend/internal/repo/repo_documents.go index e91b9a6..a9a5666 100644 --- a/backend/internal/repo/repo_documents.go +++ b/backend/internal/repo/repo_documents.go @@ -2,46 +2,117 @@ package repo import ( "context" + "errors" + "io" + "os" + "path/filepath" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/document" "github.com/hay-kot/homebox/backend/ent/group" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/pkgs/pathlib" +) + +var ( + ErrInvalidDocExtension = errors.New("invalid document extension") ) -// DocumentRepository is a repository for Document entity type DocumentRepository struct { - db *ent.Client + db *ent.Client + dir string } -func (r *DocumentRepository) Create(ctx context.Context, gid uuid.UUID, doc types.DocumentCreate) (*ent.Document, error) { - return r.db.Document.Create(). +type ( + DocumentCreate struct { + Title string `json:"title"` + Content io.Reader `json:"content"` + } + + DocumentOut struct { + ID uuid.UUID `json:"id"` + Title string `json:"title"` + Path string `json:"path"` + } +) + +func mapDocumentOut(doc *ent.Document) DocumentOut { + return DocumentOut{ + ID: doc.ID, + Title: doc.Title, + Path: doc.Path, + } +} + +var ( + mapDocumentOutErr = mapTErrFunc(mapDocumentOut) + mapDocumentOutEachErr = mapTEachErrFunc(mapDocumentOut) +) + +func (r *DocumentRepository) path(gid uuid.UUID, ext string) string { + return pathlib.Safe(filepath.Join(r.dir, gid.String(), "documents", uuid.NewString()+ext)) +} + +func (r *DocumentRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]DocumentOut, error) { + return mapDocumentOutEachErr(r.db.Document. + Query(). + Where(document.HasGroupWith(group.ID(gid))). + All(ctx), + ) +} + +func (r *DocumentRepository) Get(ctx context.Context, id uuid.UUID) (DocumentOut, error) { + return mapDocumentOutErr(r.db.Document.Get(ctx, id)) +} + +func (r *DocumentRepository) Create(ctx context.Context, gid uuid.UUID, doc DocumentCreate) (DocumentOut, error) { + ext := filepath.Ext(doc.Title) + if ext == "" { + return DocumentOut{}, ErrInvalidDocExtension + } + + path := r.path(gid, ext) + + parent := filepath.Dir(path) + err := os.MkdirAll(parent, 0755) + if err != nil { + return DocumentOut{}, err + } + + f, err := os.Create(path) + if err != nil { + return DocumentOut{}, err + } + + _, err = io.Copy(f, doc.Content) + if err != nil { + return DocumentOut{}, err + } + + return mapDocumentOutErr(r.db.Document.Create(). SetGroupID(gid). SetTitle(doc.Title). - SetPath(doc.Path). - Save(ctx) + SetPath(path). + Save(ctx), + ) } -func (r *DocumentRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]*ent.Document, error) { - return r.db.Document.Query(). - Where(document.HasGroupWith(group.ID(gid))). - All(ctx) -} - -func (r *DocumentRepository) Get(ctx context.Context, id uuid.UUID) (*ent.Document, error) { - return r.db.Document.Query(). - Where(document.ID(id)). - Only(ctx) -} - -func (r *DocumentRepository) Update(ctx context.Context, id uuid.UUID, doc types.DocumentUpdate) (*ent.Document, error) { - return r.db.Document.UpdateOneID(id). - SetTitle(doc.Title). - SetPath(doc.Path). - Save(ctx) +func (r *DocumentRepository) Rename(ctx context.Context, id uuid.UUID, title string) (DocumentOut, error) { + return mapDocumentOutErr(r.db.Document.UpdateOneID(id). + SetTitle(title). + Save(ctx)) } func (r *DocumentRepository) Delete(ctx context.Context, id uuid.UUID) error { + doc, err := r.db.Document.Get(ctx, id) + if err != nil { + return err + } + + err = os.Remove(doc.Path) + if err != nil { + return err + } + return r.db.Document.DeleteOneID(id).Exec(ctx) } diff --git a/backend/internal/repo/repo_documents_test.go b/backend/internal/repo/repo_documents_test.go index df5cd7b..06e631c 100644 --- a/backend/internal/repo/repo_documents_test.go +++ b/backend/internal/repo/repo_documents_test.go @@ -1,110 +1,28 @@ package repo import ( + "bytes" "context" + "fmt" + "os" + "path/filepath" "testing" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/stretchr/testify/assert" ) -func TestDocumentRepository_Create(t *testing.T) { - type args struct { - ctx context.Context - gid uuid.UUID - doc types.DocumentCreate - } - tests := []struct { - name string - args args - want *ent.Document - wantErr bool - }{ - { - name: "create document", - args: args{ - ctx: context.Background(), - gid: tGroup.ID, - doc: types.DocumentCreate{ - Title: "test document", - Path: "/test/document", - }, - }, - want: &ent.Document{ - Title: "test document", - Path: "/test/document", - }, - wantErr: false, - }, - { - name: "create document with empty title", - args: args{ - ctx: context.Background(), - gid: tGroup.ID, - doc: types.DocumentCreate{ - Title: "", - Path: "/test/document", - }, - }, - want: nil, - wantErr: true, - }, - { - name: "create document with empty path", - args: args{ - ctx: context.Background(), - gid: tGroup.ID, - doc: types.DocumentCreate{ - Title: "test document", - Path: "", - }, - }, - want: nil, - wantErr: true, - }, - } - ids := make([]uuid.UUID, 0, len(tests)) - - t.Cleanup(func() { - for _, id := range ids { - err := tRepos.Docs.Delete(context.Background(), id) - assert.NoError(t, err) - } - }) - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := tRepos.Docs.Create(tt.args.ctx, tt.args.gid, tt.args.doc) - if (err != nil) != tt.wantErr { - t.Errorf("DocumentRepository.Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - - if tt.wantErr { - assert.Error(t, err) - assert.Nil(t, got) - return - } - - assert.Equal(t, tt.want.Title, got.Title) - assert.Equal(t, tt.want.Path, got.Path) - ids = append(ids, got.ID) - }) - } -} - -func useDocs(t *testing.T, num int) []*ent.Document { +func useDocs(t *testing.T, num int) []DocumentOut { t.Helper() - results := make([]*ent.Document, 0, num) + results := make([]DocumentOut, 0, num) ids := make([]uuid.UUID, 0, num) for i := 0; i < num; i++ { - doc, err := tRepos.Docs.Create(context.Background(), tGroup.ID, types.DocumentCreate{ - Title: fk.Str(10), - Path: fk.Path(), + doc, err := tRepos.Docs.Create(context.Background(), tGroup.ID, DocumentCreate{ + Title: fk.Str(10) + ".md", + Content: bytes.NewReader([]byte(fk.Str(10))), }) assert.NoError(t, err) @@ -126,77 +44,68 @@ func useDocs(t *testing.T, num int) []*ent.Document { return results } -func TestDocumentRepository_GetAll(t *testing.T) { - entities := useDocs(t, 10) - - for _, entity := range entities { - assert.NotNil(t, entity) +func TestDocumentRepository_CreateUpdateDelete(t *testing.T) { + temp := t.TempDir() + r := DocumentRepository{ + db: tClient, + dir: temp, } - all, err := tRepos.Docs.GetAll(context.Background(), tGroup.ID) - assert.NoError(t, err) + type args struct { + ctx context.Context + gid uuid.UUID + doc DocumentCreate + } + tests := []struct { + name string + content string + args args + title string + wantErr bool + }{ + { + name: "basic create", + title: "test.md", + content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + args: args{ + ctx: context.Background(), + gid: tGroup.ID, + doc: DocumentCreate{ + Title: "test.md", + Content: bytes.NewReader([]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit.")), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create Document + got, err := r.Create(tt.args.ctx, tt.args.gid, tt.args.doc) + assert.NoError(t, err) + assert.Equal(t, tt.title, got.Title) + assert.Equal(t, fmt.Sprintf("%s/%s/documents", temp, tt.args.gid), filepath.Dir(got.Path)) - assert.Len(t, all, 10) - for _, entity := range all { - assert.NotNil(t, entity) - - for _, e := range entities { - if e.ID == entity.ID { - assert.Equal(t, e.Title, entity.Title) - assert.Equal(t, e.Path, entity.Path) + ensureRead := func() { + // Read Document + bts, err := os.ReadFile(got.Path) + assert.NoError(t, err) + assert.Equal(t, tt.content, string(bts)) } - } - } -} - -func TestDocumentRepository_Get(t *testing.T) { - entities := useDocs(t, 10) - - for _, entity := range entities { - got, err := tRepos.Docs.Get(context.Background(), entity.ID) - - assert.NoError(t, err) - assert.Equal(t, entity.ID, got.ID) - assert.Equal(t, entity.Title, got.Title) - assert.Equal(t, entity.Path, got.Path) - } -} - -func TestDocumentRepository_Update(t *testing.T) { - entities := useDocs(t, 10) - - for _, entity := range entities { - got, err := tRepos.Docs.Get(context.Background(), entity.ID) - - assert.NoError(t, err) - assert.Equal(t, entity.ID, got.ID) - assert.Equal(t, entity.Title, got.Title) - assert.Equal(t, entity.Path, got.Path) - } - - for _, entity := range entities { - updateData := types.DocumentUpdate{ - Title: fk.Str(10), - Path: fk.Path(), - } - - updated, err := tRepos.Docs.Update(context.Background(), entity.ID, updateData) - - assert.NoError(t, err) - assert.Equal(t, entity.ID, updated.ID) - assert.Equal(t, updateData.Title, updated.Title) - assert.Equal(t, updateData.Path, updated.Path) - } -} - -func TestDocumentRepository_Delete(t *testing.T) { - entities := useDocs(t, 10) - - for _, entity := range entities { - err := tRepos.Docs.Delete(context.Background(), entity.ID) - assert.NoError(t, err) - - _, err = tRepos.Docs.Get(context.Background(), entity.ID) - assert.Error(t, err) + ensureRead() + + // Update Document + got, err = r.Rename(tt.args.ctx, got.ID, "__"+tt.title+"__") + assert.NoError(t, err) + assert.Equal(t, "__"+tt.title+"__", got.Title) + + ensureRead() + + // Delete Document + err = r.Delete(tt.args.ctx, got.ID) + assert.NoError(t, err) + + _, err = os.Stat(got.Path) + assert.Error(t, err) + }) } } diff --git a/backend/internal/repo/repo_item_attachments.go b/backend/internal/repo/repo_item_attachments.go index 76b06d6..9b0f810 100644 --- a/backend/internal/repo/repo_item_attachments.go +++ b/backend/internal/repo/repo_item_attachments.go @@ -2,6 +2,7 @@ package repo import ( "context" + "time" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" @@ -16,6 +17,36 @@ type AttachmentRepo struct { db *ent.Client } +type ( + ItemAttachment struct { + ID uuid.UUID `json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Type string `json:"type"` + Document DocumentOut `json:"document"` + } + + ItemAttachmentUpdate struct { + ID uuid.UUID `json:"-"` + Type string `json:"type"` + Title string `json:"title"` + } +) + +func ToItemAttachment(attachment *ent.Attachment) ItemAttachment { + return ItemAttachment{ + ID: attachment.ID, + CreatedAt: attachment.CreatedAt, + UpdatedAt: attachment.UpdatedAt, + Type: attachment.Type.String(), + Document: DocumentOut{ + ID: attachment.Edges.Document.ID, + Title: attachment.Edges.Document.Title, + Path: attachment.Edges.Document.Path, + }, + } +} + func (r *AttachmentRepo) Create(ctx context.Context, itemId, docId uuid.UUID, typ attachment.Type) (*ent.Attachment, error) { return r.db.Attachment.Create(). SetType(typ). diff --git a/backend/internal/repo/repo_items.go b/backend/internal/repo/repo_items.go index ab7cd8e..e6375f7 100644 --- a/backend/internal/repo/repo_items.go +++ b/backend/internal/repo/repo_items.go @@ -2,21 +2,187 @@ package repo import ( "context" + "time" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/ent/item" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/ent/predicate" ) type ItemsRepository struct { db *ent.Client } -func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (*ent.Item, error) { - return e.db.Item.Query(). - Where(item.ID(id)). +type ( + ItemCreate struct { + ImportRef string `json:"-"` + Name string `json:"name"` + Description string `json:"description"` + + // Edges + LocationID uuid.UUID `json:"locationId"` + LabelIDs []uuid.UUID `json:"labelIds"` + } + ItemUpdate struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Quantity int `json:"quantity"` + Insured bool `json:"insured"` + + // Edges + LocationID uuid.UUID `json:"locationId"` + LabelIDs []uuid.UUID `json:"labelIds"` + + // Identifications + SerialNumber string `json:"serialNumber"` + ModelNumber string `json:"modelNumber"` + Manufacturer string `json:"manufacturer"` + + // Warranty + LifetimeWarranty bool `json:"lifetimeWarranty"` + WarrantyExpires time.Time `json:"warrantyExpires"` + WarrantyDetails string `json:"warrantyDetails"` + + // Purchase + PurchaseTime time.Time `json:"purchaseTime"` + PurchaseFrom string `json:"purchaseFrom"` + PurchasePrice float64 `json:"purchasePrice,string"` + + // Sold + SoldTime time.Time `json:"soldTime"` + SoldTo string `json:"soldTo"` + SoldPrice float64 `json:"soldPrice,string"` + SoldNotes string `json:"soldNotes"` + + // Extras + Notes string `json:"notes"` + // Fields []*FieldSummary `json:"fields"` + } + + ItemSummary struct { + ImportRef string `json:"-"` + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Quantity int `json:"quantity"` + Insured bool `json:"insured"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + + // Edges + Location LocationSummary `json:"location"` + Labels []LabelSummary `json:"labels"` + } + + ItemOut struct { + ItemSummary + + SerialNumber string `json:"serialNumber"` + ModelNumber string `json:"modelNumber"` + Manufacturer string `json:"manufacturer"` + + // Warranty + LifetimeWarranty bool `json:"lifetimeWarranty"` + WarrantyExpires time.Time `json:"warrantyExpires"` + WarrantyDetails string `json:"warrantyDetails"` + + // Purchase + PurchaseTime time.Time `json:"purchaseTime"` + PurchaseFrom string `json:"purchaseFrom"` + PurchasePrice float64 `json:"purchasePrice,string"` + + // Sold + SoldTime time.Time `json:"soldTime"` + SoldTo string `json:"soldTo"` + SoldPrice float64 `json:"soldPrice,string"` + SoldNotes string `json:"soldNotes"` + + // Extras + Notes string `json:"notes"` + + Attachments []ItemAttachment `json:"attachments"` + // Future + // Fields []*FieldSummary `json:"fields"` + } +) + +var ( + mapItemsSummaryErr = mapTEachErrFunc(mapItemSummary) +) + +func mapItemSummary(item *ent.Item) ItemSummary { + var location LocationSummary + if item.Edges.Location != nil { + location = mapLocationSummary(item.Edges.Location) + } + + var labels []LabelSummary + if item.Edges.Label != nil { + labels = mapEach(item.Edges.Label, mapLabelSummary) + } + + return ItemSummary{ + ID: item.ID, + Name: item.Name, + Description: item.Description, + Quantity: item.Quantity, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + + // Edges + Location: location, + Labels: labels, + + // Warranty + Insured: item.Insured, + } +} + +var ( + mapItemOutErr = mapTErrFunc(mapItemOut) +) + +func mapItemOut(item *ent.Item) ItemOut { + var attachments []ItemAttachment + if item.Edges.Attachments != nil { + attachments = mapEach(item.Edges.Attachments, ToItemAttachment) + } + + return ItemOut{ + ItemSummary: mapItemSummary(item), + LifetimeWarranty: item.LifetimeWarranty, + WarrantyExpires: item.WarrantyExpires, + WarrantyDetails: item.WarrantyDetails, + + // Identification + SerialNumber: item.SerialNumber, + ModelNumber: item.ModelNumber, + Manufacturer: item.Manufacturer, + + // Purchase + PurchaseTime: item.PurchaseTime, + PurchaseFrom: item.PurchaseFrom, + PurchasePrice: item.PurchasePrice, + + // Sold + SoldTime: item.SoldTime, + SoldTo: item.SoldTo, + SoldPrice: item.SoldPrice, + SoldNotes: item.SoldNotes, + + // Extras + Notes: item.Notes, + Attachments: attachments, + } +} + +func (e *ItemsRepository) getOne(ctx context.Context, where ...predicate.Item) (ItemOut, error) { + q := e.db.Item.Query().Where(where...) + + return mapItemOutErr(q. WithFields(). WithLabel(). WithLocation(). @@ -24,19 +190,32 @@ func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (*ent.Item, WithAttachments(func(aq *ent.AttachmentQuery) { aq.WithDocument() }). - Only(ctx) + Only(ctx), + ) +} + +// GetOne returns a single item by ID. If the item does not exist, an error is returned. +// See also: GetOneByGroup to ensure that the item belongs to a specific group. +func (e *ItemsRepository) GetOne(ctx context.Context, id uuid.UUID) (ItemOut, error) { + return e.getOne(ctx, item.ID(id)) +} + +// GetOneByGroup returns a single item by ID. If the item does not exist, an error is returned. +// GetOneByGroup ensures that the item belongs to a specific group. +func (e *ItemsRepository) GetOneByGroup(ctx context.Context, gid, id uuid.UUID) (ItemOut, error) { + return e.getOne(ctx, item.ID(id), item.HasGroupWith(group.ID(gid))) } // GetAll returns all the items in the database with the Labels and Locations eager loaded. -func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]*ent.Item, error) { - return e.db.Item.Query(). +func (e *ItemsRepository) GetAll(ctx context.Context, gid uuid.UUID) ([]ItemSummary, error) { + return mapItemsSummaryErr(e.db.Item.Query(). Where(item.HasGroupWith(group.ID(gid))). WithLabel(). WithLocation(). - All(ctx) + All(ctx)) } -func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data types.ItemCreate) (*ent.Item, error) { +func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data ItemCreate) (ItemOut, error) { q := e.db.Item.Create(). SetName(data.Name). SetDescription(data.Description). @@ -49,7 +228,7 @@ func (e *ItemsRepository) Create(ctx context.Context, gid uuid.UUID, data types. result, err := q.Save(ctx) if err != nil { - return nil, err + return ItemOut{}, err } return e.GetOne(ctx, result.ID) @@ -59,8 +238,18 @@ func (e *ItemsRepository) Delete(ctx context.Context, id uuid.UUID) error { return e.db.Item.DeleteOneID(id).Exec(ctx) } -func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*ent.Item, error) { - q := e.db.Item.UpdateOneID(data.ID). +func (e *ItemsRepository) DeleteByGroup(ctx context.Context, gid, id uuid.UUID) error { + _, err := e.db.Item. + Delete(). + Where( + item.ID(id), + item.HasGroupWith(group.ID(gid)), + ).Exec(ctx) + return err +} + +func (e *ItemsRepository) UpdateByGroup(ctx context.Context, gid uuid.UUID, data ItemUpdate) (ItemOut, error) { + q := e.db.Item.Update().Where(item.ID(data.ID), item.HasGroupWith(group.ID(gid))). SetName(data.Name). SetDescription(data.Description). SetLocationID(data.LocationID). @@ -83,13 +272,13 @@ func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*e currentLabels, err := e.db.Item.Query().Where(item.ID(data.ID)).QueryLabel().All(ctx) if err != nil { - return nil, err + return ItemOut{}, err } - set := EntitiesToIDSet(currentLabels) + set := newIDSet(currentLabels) for _, l := range data.LabelIDs { - if set.Has(l) { + if set.Contains(l) { set.Remove(l) continue } @@ -102,7 +291,7 @@ func (e *ItemsRepository) Update(ctx context.Context, data types.ItemUpdate) (*e err = q.Exec(ctx) if err != nil { - return nil, err + return ItemOut{}, err } return e.GetOne(ctx, data.ID) diff --git a/backend/internal/repo/repo_items_test.go b/backend/internal/repo/repo_items_test.go index 5917afe..f600ad1 100644 --- a/backend/internal/repo/repo_items_test.go +++ b/backend/internal/repo/repo_items_test.go @@ -6,25 +6,23 @@ import ( "time" "github.com/google/uuid" - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/stretchr/testify/assert" ) -func itemFactory() types.ItemCreate { - return types.ItemCreate{ +func itemFactory() ItemCreate { + return ItemCreate{ Name: fk.Str(10), Description: fk.Str(100), } } -func useItems(t *testing.T, len int) []*ent.Item { +func useItems(t *testing.T, len int) []ItemOut { t.Helper() location, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory()) assert.NoError(t, err) - items := make([]*ent.Item, len) + items := make([]ItemOut, len) for i := 0; i < len; i++ { itm := itemFactory() itm.LocationID = location.ID @@ -107,7 +105,7 @@ func TestItemsRepository_Create_Location(t *testing.T) { foundItem, err := tRepos.Items.GetOne(context.Background(), result.ID) assert.NoError(t, err) assert.Equal(t, result.ID, foundItem.ID) - assert.Equal(t, location.ID, foundItem.Edges.Location.ID) + assert.Equal(t, location.ID, foundItem.Location.ID) // Cleanup - Also deletes item err = tRepos.Locations.Delete(context.Background(), location.ID) @@ -168,18 +166,18 @@ func TestItemsRepository_Update_Labels(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Apply all labels to entity - updateData := types.ItemUpdate{ + updateData := ItemUpdate{ ID: entity.ID, Name: entity.Name, - LocationID: entity.Edges.Location.ID, + LocationID: entity.Location.ID, LabelIDs: tt.args.labelIds, } - updated, err := tRepos.Items.Update(context.Background(), updateData) + updated, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, updateData) assert.NoError(t, err) - assert.Len(t, tt.want, len(updated.Edges.Label)) + assert.Len(t, tt.want, len(updated.Labels)) - for _, label := range updated.Edges.Label { + for _, label := range updated.Labels { assert.Contains(t, tt.want, label.ID) } }) @@ -192,10 +190,10 @@ func TestItemsRepository_Update(t *testing.T) { entity := entities[0] - updateData := types.ItemUpdate{ + updateData := ItemUpdate{ ID: entity.ID, Name: entity.Name, - LocationID: entity.Edges.Location.ID, + LocationID: entity.Location.ID, SerialNumber: fk.Str(10), LabelIDs: nil, ModelNumber: fk.Str(10), @@ -213,7 +211,7 @@ func TestItemsRepository_Update(t *testing.T) { LifetimeWarranty: true, } - updatedEntity, err := tRepos.Items.Update(context.Background(), updateData) + updatedEntity, err := tRepos.Items.UpdateByGroup(context.Background(), tGroup.ID, updateData) assert.NoError(t, err) got, err := tRepos.Items.GetOne(context.Background(), updatedEntity.ID) @@ -221,7 +219,7 @@ func TestItemsRepository_Update(t *testing.T) { assert.Equal(t, updateData.ID, got.ID) assert.Equal(t, updateData.Name, got.Name) - assert.Equal(t, updateData.LocationID, got.Edges.Location.ID) + assert.Equal(t, updateData.LocationID, got.Location.ID) assert.Equal(t, updateData.SerialNumber, got.SerialNumber) assert.Equal(t, updateData.ModelNumber, got.ModelNumber) assert.Equal(t, updateData.Manufacturer, got.Manufacturer) diff --git a/backend/internal/repo/repo_labels.go b/backend/internal/repo/repo_labels.go index 1278068..567b3f8 100644 --- a/backend/internal/repo/repo_labels.go +++ b/backend/internal/repo/repo_labels.go @@ -2,34 +2,94 @@ package repo import ( "context" + "time" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/ent/label" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/ent/predicate" ) type LabelRepository struct { db *ent.Client } +type ( + LabelCreate struct { + Name string `json:"name"` + Description string `json:"description"` + Color string `json:"color"` + } -func (r *LabelRepository) Get(ctx context.Context, ID uuid.UUID) (*ent.Label, error) { - return r.db.Label.Query(). - Where(label.ID(ID)). + LabelUpdate struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Color string `json:"color"` + } + + LabelSummary struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + } + + LabelOut struct { + LabelSummary + Items []ItemSummary `json:"items"` + } +) + +func mapLabelSummary(label *ent.Label) LabelSummary { + return LabelSummary{ + ID: label.ID, + Name: label.Name, + Description: label.Description, + CreatedAt: label.CreatedAt, + UpdatedAt: label.UpdatedAt, + } +} + +var ( + mapLabelOutErr = mapTErrFunc(mapLabelOut) + mapLabelsOut = mapTEachErrFunc(mapLabelSummary) +) + +func mapLabelOut(label *ent.Label) LabelOut { + return LabelOut{ + LabelSummary: mapLabelSummary(label), + Items: mapEach(label.Edges.Items, mapItemSummary), + } +} + +func (r *LabelRepository) getOne(ctx context.Context, where ...predicate.Label) (LabelOut, error) { + return mapLabelOutErr(r.db.Label.Query(). + Where(where...). WithGroup(). WithItems(). - Only(ctx) + Only(ctx), + ) } -func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]*ent.Label, error) { - return r.db.Label.Query(). +func (r *LabelRepository) GetOne(ctx context.Context, ID uuid.UUID) (LabelOut, error) { + return r.getOne(ctx, label.ID(ID)) +} + +func (r *LabelRepository) GetOneByGroup(ctx context.Context, gid, ld uuid.UUID) (LabelOut, error) { + return r.getOne(ctx, label.ID(ld), label.HasGroupWith(group.ID(gid))) +} + +func (r *LabelRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LabelSummary, error) { + return mapLabelsOut(r.db.Label.Query(). Where(label.HasGroupWith(group.ID(groupId))). WithGroup(). - All(ctx) + All(ctx), + ) } -func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data types.LabelCreate) (*ent.Label, error) { +func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data LabelCreate) (LabelOut, error) { label, err := r.db.Label.Create(). SetName(data.Name). SetDescription(data.Description). @@ -37,11 +97,15 @@ func (r *LabelRepository) Create(ctx context.Context, groupdId uuid.UUID, data t SetGroupID(groupdId). Save(ctx) + if err != nil { + return LabelOut{}, err + } + label.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID - return label, err + return mapLabelOut(label), err } -func (r *LabelRepository) Update(ctx context.Context, data types.LabelUpdate) (*ent.Label, error) { +func (r *LabelRepository) Update(ctx context.Context, data LabelUpdate) (LabelOut, error) { _, err := r.db.Label.UpdateOneID(data.ID). SetName(data.Name). SetDescription(data.Description). @@ -49,10 +113,10 @@ func (r *LabelRepository) Update(ctx context.Context, data types.LabelUpdate) (* Save(ctx) if err != nil { - return nil, err + return LabelOut{}, err } - return r.Get(ctx, data.ID) + return r.GetOne(ctx, data.ID) } func (r *LabelRepository) Delete(ctx context.Context, id uuid.UUID) error { diff --git a/backend/internal/repo/repo_labels_test.go b/backend/internal/repo/repo_labels_test.go index 4c9753a..691b915 100644 --- a/backend/internal/repo/repo_labels_test.go +++ b/backend/internal/repo/repo_labels_test.go @@ -4,22 +4,20 @@ import ( "context" "testing" - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/stretchr/testify/assert" ) -func labelFactory() types.LabelCreate { - return types.LabelCreate{ +func labelFactory() LabelCreate { + return LabelCreate{ Name: fk.Str(10), Description: fk.Str(100), } } -func useLabels(t *testing.T, len int) []*ent.Label { +func useLabels(t *testing.T, len int) []LabelOut { t.Helper() - labels := make([]*ent.Label, len) + labels := make([]LabelOut, len) for i := 0; i < len; i++ { itm := labelFactory() @@ -42,7 +40,7 @@ func TestLabelRepository_Get(t *testing.T) { label := labels[0] // Get by ID - foundLoc, err := tRepos.Labels.Get(context.Background(), label.ID) + foundLoc, err := tRepos.Labels.GetOne(context.Background(), label.ID) assert.NoError(t, err) assert.Equal(t, label.ID, foundLoc.ID) } @@ -60,7 +58,7 @@ func TestLabelRepository_Create(t *testing.T) { assert.NoError(t, err) // Get by ID - foundLoc, err := tRepos.Labels.Get(context.Background(), loc.ID) + foundLoc, err := tRepos.Labels.GetOne(context.Background(), loc.ID) assert.NoError(t, err) assert.Equal(t, loc.ID, foundLoc.ID) @@ -72,7 +70,7 @@ func TestLabelRepository_Update(t *testing.T) { loc, err := tRepos.Labels.Create(context.Background(), tGroup.ID, labelFactory()) assert.NoError(t, err) - updateData := types.LabelUpdate{ + updateData := LabelUpdate{ ID: loc.ID, Name: fk.Str(10), Description: fk.Str(100), @@ -81,7 +79,7 @@ func TestLabelRepository_Update(t *testing.T) { update, err := tRepos.Labels.Update(context.Background(), updateData) assert.NoError(t, err) - foundLoc, err := tRepos.Labels.Get(context.Background(), loc.ID) + foundLoc, err := tRepos.Labels.GetOne(context.Background(), loc.ID) assert.NoError(t, err) assert.Equal(t, update.ID, foundLoc.ID) @@ -99,6 +97,6 @@ func TestLabelRepository_Delete(t *testing.T) { err = tRepos.Labels.Delete(context.Background(), loc.ID) assert.NoError(t, err) - _, err = tRepos.Labels.Get(context.Background(), loc.ID) + _, err = tRepos.Labels.GetOne(context.Background(), loc.ID) assert.Error(t, err) } diff --git a/backend/internal/repo/repo_locations.go b/backend/internal/repo/repo_locations.go index cb224e2..7494690 100644 --- a/backend/internal/repo/repo_locations.go +++ b/backend/internal/repo/repo_locations.go @@ -2,24 +2,79 @@ package repo import ( "context" + "time" "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" + "github.com/hay-kot/homebox/backend/ent/group" "github.com/hay-kot/homebox/backend/ent/location" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/ent/predicate" ) type LocationRepository struct { db *ent.Client } -type LocationWithCount struct { - *ent.Location - ItemCount int `json:"itemCount"` +type ( + LocationCreate struct { + Name string `json:"name"` + Description string `json:"description"` + } + + LocationUpdate struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + } + + LocationSummary struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + } + + LocationOutCount struct { + LocationSummary + ItemCount int `json:"itemCount"` + } + + LocationOut struct { + LocationSummary + Items []ItemSummary `json:"items"` + } +) + +func mapLocationSummary(location *ent.Location) LocationSummary { + return LocationSummary{ + ID: location.ID, + Name: location.Name, + Description: location.Description, + CreatedAt: location.CreatedAt, + UpdatedAt: location.UpdatedAt, + } +} + +var ( + mapLocationOutErr = mapTErrFunc(mapLocationOut) +) + +func mapLocationOut(location *ent.Location) LocationOut { + return LocationOut{ + LocationSummary: LocationSummary{ + ID: location.ID, + Name: location.Name, + Description: location.Description, + CreatedAt: location.CreatedAt, + UpdatedAt: location.UpdatedAt, + }, + Items: mapEach(location.Edges.Items, mapItemSummary), + } } // GetALlWithCount returns all locations with item count field populated -func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LocationWithCount, error) { +func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]LocationOutCount, error) { query := `--sql SELECT id, @@ -46,54 +101,62 @@ func (r *LocationRepository) GetAll(ctx context.Context, groupId uuid.UUID) ([]L return nil, err } - list := []LocationWithCount{} + list := []LocationOutCount{} for rows.Next() { - var loc ent.Location - var ct LocationWithCount - err := rows.Scan(&loc.ID, &loc.Name, &loc.Description, &loc.CreatedAt, &loc.UpdatedAt, &ct.ItemCount) + var ct LocationOutCount + + err := rows.Scan(&ct.ID, &ct.Name, &ct.Description, &ct.CreatedAt, &ct.UpdatedAt, &ct.ItemCount) if err != nil { return nil, err } - ct.Location = &loc + list = append(list, ct) } return list, err } -func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (*ent.Location, error) { - return r.db.Location.Query(). - Where(location.ID(ID)). +func (r *LocationRepository) getOne(ctx context.Context, where ...predicate.Location) (LocationOut, error) { + return mapLocationOutErr(r.db.Location.Query(). + Where(where...). WithGroup(). WithItems(func(iq *ent.ItemQuery) { iq.WithLabel() }). - Only(ctx) + Only(ctx)) } -func (r *LocationRepository) Create(ctx context.Context, groupdId uuid.UUID, data types.LocationCreate) (*ent.Location, error) { +func (r *LocationRepository) Get(ctx context.Context, ID uuid.UUID) (LocationOut, error) { + return r.getOne(ctx, location.ID(ID)) +} + +func (r *LocationRepository) GetOneByGroup(ctx context.Context, GID, ID uuid.UUID) (LocationOut, error) { + return r.getOne(ctx, location.ID(ID), location.HasGroupWith(group.ID(GID))) +} + +func (r *LocationRepository) Create(ctx context.Context, gid uuid.UUID, data LocationCreate) (LocationOut, error) { location, err := r.db.Location.Create(). SetName(data.Name). SetDescription(data.Description). - SetGroupID(groupdId). + SetGroupID(gid). Save(ctx) if err != nil { - return nil, err + return LocationOut{}, err } - location.Edges.Group = &ent.Group{ID: groupdId} // bootstrap group ID - return location, err + location.Edges.Group = &ent.Group{ID: gid} // bootstrap group ID + return mapLocationOut(location), nil } -func (r *LocationRepository) Update(ctx context.Context, data types.LocationUpdate) (*ent.Location, error) { +func (r *LocationRepository) Update(ctx context.Context, data LocationUpdate) (LocationOut, error) { _, err := r.db.Location.UpdateOneID(data.ID). SetName(data.Name). SetDescription(data.Description). Save(ctx) if err != nil { - return nil, err + return LocationOut{}, err } return r.Get(ctx, data.ID) diff --git a/backend/internal/repo/repo_locations_test.go b/backend/internal/repo/repo_locations_test.go index 6c95c5d..d48779d 100644 --- a/backend/internal/repo/repo_locations_test.go +++ b/backend/internal/repo/repo_locations_test.go @@ -4,12 +4,11 @@ import ( "context" "testing" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/stretchr/testify/assert" ) -func locationFactory() types.LocationCreate { - return types.LocationCreate{ +func locationFactory() LocationCreate { + return LocationCreate{ Name: fk.Str(10), Description: fk.Str(100), } @@ -30,13 +29,13 @@ func TestLocationRepository_Get(t *testing.T) { func TestLocationRepositoryGetAllWithCount(t *testing.T) { ctx := context.Background() - result, err := tRepos.Locations.Create(ctx, tGroup.ID, types.LocationCreate{ + result, err := tRepos.Locations.Create(ctx, tGroup.ID, LocationCreate{ Name: fk.Str(10), Description: fk.Str(100), }) assert.NoError(t, err) - _, err = tRepos.Items.Create(ctx, tGroup.ID, types.ItemCreate{ + _, err = tRepos.Items.Create(ctx, tGroup.ID, ItemCreate{ Name: fk.Str(10), Description: fk.Str(100), LocationID: result.ID, @@ -72,7 +71,7 @@ func TestLocationRepository_Update(t *testing.T) { loc, err := tRepos.Locations.Create(context.Background(), tGroup.ID, locationFactory()) assert.NoError(t, err) - updateData := types.LocationUpdate{ + updateData := LocationUpdate{ ID: loc.ID, Name: fk.Str(10), Description: fk.Str(100), diff --git a/backend/internal/repo/repo_tokens.go b/backend/internal/repo/repo_tokens.go index d452b31..38c7443 100644 --- a/backend/internal/repo/repo_tokens.go +++ b/backend/internal/repo/repo_tokens.go @@ -4,17 +4,34 @@ import ( "context" "time" + "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/authtokens" - "github.com/hay-kot/homebox/backend/internal/types" ) type TokenRepository struct { db *ent.Client } +type ( + UserAuthTokenCreate struct { + TokenHash []byte `json:"token"` + UserID uuid.UUID `json:"userId"` + ExpiresAt time.Time `json:"expiresAt"` + } + + UserAuthToken struct { + UserAuthTokenCreate + CreatedAt time.Time `json:"createdAt"` + } +) + +func (u UserAuthToken) IsExpired() bool { + return u.ExpiresAt.Before(time.Now()) +} + // GetUserFromToken get's a user from a token -func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (*ent.User, error) { +func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (UserOut, error) { user, err := r.db.AuthTokens.Query(). Where(authtokens.Token(token)). Where(authtokens.ExpiresAtGTE(time.Now())). @@ -24,15 +41,14 @@ func (r *TokenRepository) GetUserFromToken(ctx context.Context, token []byte) (* Only(ctx) if err != nil { - return nil, err + return UserOut{}, err } - return user, nil + return mapUserOut(user), nil } // Creates a token for a user -func (r *TokenRepository) CreateToken(ctx context.Context, createToken types.UserAuthTokenCreate) (types.UserAuthToken, error) { - tokenOut := types.UserAuthToken{} +func (r *TokenRepository) CreateToken(ctx context.Context, createToken UserAuthTokenCreate) (UserAuthToken, error) { dbToken, err := r.db.AuthTokens.Create(). SetToken(createToken.TokenHash). @@ -41,15 +57,17 @@ func (r *TokenRepository) CreateToken(ctx context.Context, createToken types.Use Save(ctx) if err != nil { - return tokenOut, err + return UserAuthToken{}, err } - tokenOut.TokenHash = dbToken.Token - tokenOut.UserID = createToken.UserID - tokenOut.CreatedAt = dbToken.CreatedAt - tokenOut.ExpiresAt = dbToken.ExpiresAt - - return tokenOut, nil + return UserAuthToken{ + UserAuthTokenCreate: UserAuthTokenCreate{ + TokenHash: dbToken.Token, + UserID: createToken.UserID, + ExpiresAt: dbToken.ExpiresAt, + }, + CreatedAt: dbToken.CreatedAt, + }, nil } // DeleteToken remove a single token from the database - equivalent to revoke or logout diff --git a/backend/internal/repo/repo_tokens_test.go b/backend/internal/repo/repo_tokens_test.go index a0bc654..e066911 100644 --- a/backend/internal/repo/repo_tokens_test.go +++ b/backend/internal/repo/repo_tokens_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/hasher" "github.com/stretchr/testify/assert" ) @@ -22,7 +21,7 @@ func TestAuthTokenRepo_CreateToken(t *testing.T) { generatedToken := hasher.GenerateToken() - token, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{ + token, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{ TokenHash: generatedToken.Hash, ExpiresAt: expiresAt, UserID: userOut.ID, @@ -50,7 +49,7 @@ func TestAuthTokenRepo_DeleteToken(t *testing.T) { generatedToken := hasher.GenerateToken() - _, err = tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{ + _, err = tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{ TokenHash: generatedToken.Hash, ExpiresAt: expiresAt, UserID: userOut.ID, @@ -72,7 +71,7 @@ func TestAuthTokenRepo_GetUserByToken(t *testing.T) { expiresAt := time.Now().Add(time.Hour) generatedToken := hasher.GenerateToken() - token, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{ + token, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{ TokenHash: generatedToken.Hash, ExpiresAt: expiresAt, UserID: userOut.ID, @@ -101,13 +100,13 @@ func TestAuthTokenRepo_PurgeExpiredTokens(t *testing.T) { user := userFactory() userOut, _ := tRepos.Users.Create(ctx, user) - createdTokens := []types.UserAuthToken{} + createdTokens := []UserAuthToken{} for i := 0; i < 5; i++ { expiresAt := time.Now() generatedToken := hasher.GenerateToken() - createdToken, err := tRepos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{ + createdToken, err := tRepos.AuthTokens.CreateToken(ctx, UserAuthTokenCreate{ TokenHash: generatedToken.Hash, ExpiresAt: expiresAt, UserID: userOut.ID, diff --git a/backend/internal/repo/repo_users.go b/backend/internal/repo/repo_users.go index ad6cd1b..f05dbb6 100644 --- a/backend/internal/repo/repo_users.go +++ b/backend/internal/repo/repo_users.go @@ -6,37 +6,77 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/user" - "github.com/hay-kot/homebox/backend/internal/types" ) type UserRepository struct { db *ent.Client } -func (e *UserRepository) GetOneId(ctx context.Context, id uuid.UUID) (*ent.User, error) { - return e.db.User.Query(). - Where(user.ID(id)). - WithGroup(). - Only(ctx) -} - -func (e *UserRepository) GetOneEmail(ctx context.Context, email string) (*ent.User, error) { - return e.db.User.Query(). - Where(user.Email(email)). - WithGroup(). - Only(ctx) -} - -func (e *UserRepository) GetAll(ctx context.Context) ([]*ent.User, error) { - return e.db.User.Query().WithGroup().All(ctx) -} - -func (e *UserRepository) Create(ctx context.Context, usr types.UserCreate) (*ent.User, error) { - err := usr.Validate() - if err != nil { - return &ent.User{}, err +type ( + // UserCreate is the Data object contain the requirements of creating a user + // in the database. It should to create users from an API unless the user has + // rights to create SuperUsers. For regular user in data use the UserIn struct. + UserCreate struct { + Name string `json:"name"` + Email string `json:"email"` + Password string `json:"password"` + IsSuperuser bool `json:"isSuperuser"` + GroupID uuid.UUID `json:"groupID"` } + UserUpdate struct { + Name string `json:"name"` + Email string `json:"email"` + } + + UserOut struct { + ID uuid.UUID `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + IsSuperuser bool `json:"isSuperuser"` + GroupID uuid.UUID `json:"groupId"` + GroupName string `json:"groupName"` + PasswordHash string `json:"-"` + } +) + +var ( + mapUserOutErr = mapTErrFunc(mapUserOut) + mapUsersOutErr = mapTEachErrFunc(mapUserOut) +) + +func mapUserOut(user *ent.User) UserOut { + return UserOut{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + IsSuperuser: user.IsSuperuser, + GroupID: user.Edges.Group.ID, + GroupName: user.Edges.Group.Name, + PasswordHash: user.Password, + } +} + +func (e *UserRepository) GetOneId(ctx context.Context, id uuid.UUID) (UserOut, error) { + return mapUserOutErr(e.db.User.Query(). + Where(user.ID(id)). + WithGroup(). + Only(ctx)) +} + +func (e *UserRepository) GetOneEmail(ctx context.Context, email string) (UserOut, error) { + return mapUserOutErr(e.db.User.Query(). + Where(user.Email(email)). + WithGroup(). + Only(ctx), + ) +} + +func (e *UserRepository) GetAll(ctx context.Context) ([]UserOut, error) { + return mapUsersOutErr(e.db.User.Query().WithGroup().All(ctx)) +} + +func (e *UserRepository) Create(ctx context.Context, usr UserCreate) (UserOut, error) { entUser, err := e.db.User. Create(). SetName(usr.Name). @@ -46,13 +86,13 @@ func (e *UserRepository) Create(ctx context.Context, usr types.UserCreate) (*ent SetGroupID(usr.GroupID). Save(ctx) if err != nil { - return entUser, err + return UserOut{}, err } return e.GetOneId(ctx, entUser.ID) } -func (e *UserRepository) Update(ctx context.Context, ID uuid.UUID, data types.UserUpdate) error { +func (e *UserRepository) Update(ctx context.Context, ID uuid.UUID, data UserUpdate) error { q := e.db.User.Update(). Where(user.ID(ID)). SetName(data.Name). diff --git a/backend/internal/repo/repo_users_test.go b/backend/internal/repo/repo_users_test.go index 27e3ece..882de1c 100644 --- a/backend/internal/repo/repo_users_test.go +++ b/backend/internal/repo/repo_users_test.go @@ -5,14 +5,11 @@ import ( "fmt" "testing" - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/stretchr/testify/assert" ) -func userFactory() types.UserCreate { - - return types.UserCreate{ +func userFactory() UserCreate { + return UserCreate{ Name: fk.Str(10), Email: fk.Email(), Password: fk.Str(10), @@ -61,7 +58,7 @@ func TestUserRepo_GetOneId(t *testing.T) { func TestUserRepo_GetAll(t *testing.T) { // Setup - toCreate := []types.UserCreate{ + toCreate := []UserCreate{ userFactory(), userFactory(), userFactory(), @@ -70,7 +67,7 @@ func TestUserRepo_GetAll(t *testing.T) { ctx := context.Background() - created := []*ent.User{} + created := []UserOut{} for _, usr := range toCreate { usrOut, _ := tRepos.Users.Create(ctx, usr) @@ -90,7 +87,7 @@ func TestUserRepo_GetAll(t *testing.T) { assert.Equal(t, usr.Email, usr2.Email) // Check groups are loaded - assert.NotNil(t, usr2.Edges.Group) + assert.NotNil(t, usr2.GroupID) } } } @@ -108,7 +105,7 @@ func TestUserRepo_Update(t *testing.T) { user, err := tRepos.Users.Create(context.Background(), userFactory()) assert.NoError(t, err) - updateData := types.UserUpdate{ + updateData := UserUpdate{ Name: fk.Str(10), Email: fk.Email(), } diff --git a/backend/internal/repo/repos_all.go b/backend/internal/repo/repos_all.go index c47f5a4..88ca1e4 100644 --- a/backend/internal/repo/repos_all.go +++ b/backend/internal/repo/repos_all.go @@ -15,7 +15,7 @@ type AllRepos struct { Attachments *AttachmentRepo } -func EntAllRepos(db *ent.Client) *AllRepos { +func EntAllRepos(db *ent.Client, root string) *AllRepos { return &AllRepos{ Users: &UserRepository{db}, AuthTokens: &TokenRepository{db}, @@ -23,7 +23,7 @@ func EntAllRepos(db *ent.Client) *AllRepos { Locations: &LocationRepository{db}, Labels: &LabelRepository{db}, Items: &ItemsRepository{db}, - Docs: &DocumentRepository{db}, + Docs: &DocumentRepository{db, root}, DocTokens: &DocumentTokensRepository{db}, Attachments: &AttachmentRepo{db}, } diff --git a/backend/internal/services/all.go b/backend/internal/services/all.go index e255e28..a8e2e8e 100644 --- a/backend/internal/services/all.go +++ b/backend/internal/services/all.go @@ -4,29 +4,23 @@ import "github.com/hay-kot/homebox/backend/internal/repo" type AllServices struct { User *UserService - Admin *AdminService Location *LocationService Labels *LabelService Items *ItemService } -func NewServices(repos *repo.AllRepos, root string) *AllServices { +func NewServices(repos *repo.AllRepos) *AllServices { if repos == nil { panic("repos cannot be nil") } - if root == "" { - panic("root cannot be empty") - } return &AllServices{ User: &UserService{repos}, - Admin: &AdminService{repos}, Location: &LocationService{repos}, Labels: &LabelService{repos}, Items: &ItemService{ - repo: repos, - filepath: root, - at: attachmentTokens{}, + repo: repos, + at: attachmentTokens{}, }, } } diff --git a/backend/internal/services/contexts.go b/backend/internal/services/contexts.go index 94a2168..72ef84f 100644 --- a/backend/internal/services/contexts.go +++ b/backend/internal/services/contexts.go @@ -4,7 +4,7 @@ import ( "context" "github.com/google/uuid" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/internal/repo" ) type contextKeys struct { @@ -26,7 +26,7 @@ type Context struct { GID uuid.UUID // User is the acting user. - User *types.UserOut + User *repo.UserOut } // NewContext is a helper function that returns the service context from the context. @@ -43,16 +43,16 @@ func NewContext(ctx context.Context) Context { // SetUserCtx is a helper function that sets the ContextUser and ContextUserToken // values within the context of a web request (or any context). -func SetUserCtx(ctx context.Context, user *types.UserOut, token string) context.Context { +func SetUserCtx(ctx context.Context, user *repo.UserOut, token string) context.Context { ctx = context.WithValue(ctx, ContextUser, user) ctx = context.WithValue(ctx, ContextUserToken, token) return ctx } // UseUserCtx is a helper function that returns the user from the context. -func UseUserCtx(ctx context.Context) *types.UserOut { +func UseUserCtx(ctx context.Context) *repo.UserOut { if val := ctx.Value(ContextUser); val != nil { - return val.(*types.UserOut) + return val.(*repo.UserOut) } return nil } diff --git a/backend/internal/services/contexts_test.go b/backend/internal/services/contexts_test.go index bcbf270..2462258 100644 --- a/backend/internal/services/contexts_test.go +++ b/backend/internal/services/contexts_test.go @@ -5,12 +5,12 @@ import ( "testing" "github.com/google/uuid" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/stretchr/testify/assert" ) func Test_SetAuthContext(t *testing.T) { - user := &types.UserOut{ + user := &repo.UserOut{ ID: uuid.New(), } diff --git a/backend/internal/services/main_test.go b/backend/internal/services/main_test.go index df0369f..03d25e4 100644 --- a/backend/internal/services/main_test.go +++ b/backend/internal/services/main_test.go @@ -10,7 +10,6 @@ import ( "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/faker" _ "github.com/mattn/go-sqlite3" ) @@ -21,7 +20,7 @@ var ( tCtx = Context{} tClient *ent.Client tRepos *repo.AllRepos - tUser *ent.User + tUser repo.UserOut tGroup *ent.Group tSvc *AllServices ) @@ -37,7 +36,7 @@ func bootstrap() { log.Fatal(err) } - tUser, err = tRepos.Users.Create(ctx, types.UserCreate{ + tUser, err = tRepos.Users.Create(ctx, repo.UserCreate{ Name: fk.Str(10), Email: fk.Email(), Password: fk.Str(10), @@ -63,11 +62,10 @@ func TestMain(m *testing.M) { } tClient = client - tRepos = repo.EntAllRepos(tClient) - tSvc = NewServices(tRepos, "/tmp/homebox") + tRepos = repo.EntAllRepos(tClient, os.TempDir()+"/homebox") + tSvc = NewServices(tRepos) defer client.Close() - bootstrap() tCtx = Context{ Context: context.Background(), diff --git a/backend/internal/services/mappers/helpers.go b/backend/internal/services/mappers/helpers.go deleted file mode 100644 index 1ffbf7a..0000000 --- a/backend/internal/services/mappers/helpers.go +++ /dev/null @@ -1,9 +0,0 @@ -package mappers - -func MapEach[T any, U any](items []T, fn func(T) U) []U { - result := make([]U, len(items)) - for i, item := range items { - result[i] = fn(item) - } - return result -} diff --git a/backend/internal/services/mappers/items.go b/backend/internal/services/mappers/items.go deleted file mode 100644 index 6b9df8d..0000000 --- a/backend/internal/services/mappers/items.go +++ /dev/null @@ -1,91 +0,0 @@ -package mappers - -import ( - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" -) - -func ToItemAttachment(attachment *ent.Attachment) *types.ItemAttachment { - return &types.ItemAttachment{ - ID: attachment.ID, - CreatedAt: attachment.CreatedAt, - UpdatedAt: attachment.UpdatedAt, - Type: attachment.Type.String(), - Document: types.DocumentOut{ - ID: attachment.Edges.Document.ID, - Title: attachment.Edges.Document.Title, - Path: attachment.Edges.Document.Path, - }, - } -} - -func ToItemSummary(item *ent.Item) *types.ItemSummary { - var location *types.LocationSummary - if item.Edges.Location != nil { - location = ToLocationSummary(item.Edges.Location) - } - - var labels []*types.LabelSummary - if item.Edges.Label != nil { - labels = MapEach(item.Edges.Label, ToLabelSummary) - } - - return &types.ItemSummary{ - ID: item.ID, - Name: item.Name, - Description: item.Description, - CreatedAt: item.CreatedAt, - UpdatedAt: item.UpdatedAt, - - Quantity: item.Quantity, - Insured: item.Insured, - - // Warranty - LifetimeWarranty: item.LifetimeWarranty, - WarrantyExpires: item.WarrantyExpires, - WarrantyDetails: item.WarrantyDetails, - - // Edges - Location: location, - Labels: labels, - - // Identification - SerialNumber: item.SerialNumber, - ModelNumber: item.ModelNumber, - Manufacturer: item.Manufacturer, - - // Purchase - PurchaseTime: item.PurchaseTime, - PurchaseFrom: item.PurchaseFrom, - PurchasePrice: item.PurchasePrice, - - // Sold - SoldTime: item.SoldTime, - SoldTo: item.SoldTo, - SoldPrice: item.SoldPrice, - SoldNotes: item.SoldNotes, - - // Extras - Notes: item.Notes, - } -} - -func ToItemSummaryErr(item *ent.Item, err error) (*types.ItemSummary, error) { - return ToItemSummary(item), err -} - -func ToItemOut(item *ent.Item) *types.ItemOut { - var attachments []*types.ItemAttachment - if item.Edges.Attachments != nil { - attachments = MapEach(item.Edges.Attachments, ToItemAttachment) - } - - return &types.ItemOut{ - ItemSummary: *ToItemSummary(item), - Attachments: attachments, - } -} - -func ToItemOutErr(item *ent.Item, err error) (*types.ItemOut, error) { - return ToItemOut(item), err -} diff --git a/backend/internal/services/mappers/labels.go b/backend/internal/services/mappers/labels.go deleted file mode 100644 index a593784..0000000 --- a/backend/internal/services/mappers/labels.go +++ /dev/null @@ -1,31 +0,0 @@ -package mappers - -import ( - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" -) - -func ToLabelSummary(label *ent.Label) *types.LabelSummary { - return &types.LabelSummary{ - ID: label.ID, - Name: label.Name, - Description: label.Description, - CreatedAt: label.CreatedAt, - UpdatedAt: label.UpdatedAt, - } -} - -func ToLabelSummaryErr(label *ent.Label, err error) (*types.LabelSummary, error) { - return ToLabelSummary(label), err -} - -func ToLabelOut(label *ent.Label) *types.LabelOut { - return &types.LabelOut{ - LabelSummary: *ToLabelSummary(label), - Items: MapEach(label.Edges.Items, ToItemSummary), - } -} - -func ToLabelOutErr(label *ent.Label, err error) (*types.LabelOut, error) { - return ToLabelOut(label), err -} diff --git a/backend/internal/services/mappers/locations.go b/backend/internal/services/mappers/locations.go deleted file mode 100644 index 6b4bc5f..0000000 --- a/backend/internal/services/mappers/locations.go +++ /dev/null @@ -1,55 +0,0 @@ -package mappers - -import ( - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/types" -) - -func ToLocationCount(location *repo.LocationWithCount) *types.LocationCount { - return &types.LocationCount{ - LocationSummary: types.LocationSummary{ - ID: location.ID, - Name: location.Name, - Description: location.Description, - CreatedAt: location.CreatedAt, - UpdatedAt: location.UpdatedAt, - }, - ItemCount: location.ItemCount, - } -} - -func ToLocationCountErr(location *repo.LocationWithCount, err error) (*types.LocationCount, error) { - return ToLocationCount(location), err -} - -func ToLocationSummary(location *ent.Location) *types.LocationSummary { - return &types.LocationSummary{ - ID: location.ID, - Name: location.Name, - Description: location.Description, - CreatedAt: location.CreatedAt, - UpdatedAt: location.UpdatedAt, - } -} - -func ToLocationSummaryErr(location *ent.Location, err error) (*types.LocationSummary, error) { - return ToLocationSummary(location), err -} - -func ToLocationOut(location *ent.Location) *types.LocationOut { - return &types.LocationOut{ - LocationSummary: types.LocationSummary{ - ID: location.ID, - Name: location.Name, - Description: location.Description, - CreatedAt: location.CreatedAt, - UpdatedAt: location.UpdatedAt, - }, - Items: MapEach(location.Edges.Items, ToItemSummary), - } -} - -func ToLocationOutErr(location *ent.Location, err error) (*types.LocationOut, error) { - return ToLocationOut(location), err -} diff --git a/backend/internal/services/mappers/user.go b/backend/internal/services/mappers/user.go deleted file mode 100644 index f5736fb..0000000 --- a/backend/internal/services/mappers/user.go +++ /dev/null @@ -1,20 +0,0 @@ -package mappers - -import ( - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/types" -) - -func ToOutUser(user *ent.User, err error) (*types.UserOut, error) { - if err != nil { - return &types.UserOut{}, err - } - return &types.UserOut{ - ID: user.ID, - Name: user.Name, - Email: user.Email, - IsSuperuser: user.IsSuperuser, - GroupName: user.Edges.Group.Name, - GroupID: user.Edges.Group.ID, - }, nil -} diff --git a/backend/internal/services/service_admin.go b/backend/internal/services/service_admin.go deleted file mode 100644 index dfc643a..0000000 --- a/backend/internal/services/service_admin.go +++ /dev/null @@ -1,48 +0,0 @@ -package services - -import ( - "context" - - "github.com/google/uuid" - "github.com/hay-kot/homebox/backend/ent" - "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/types" -) - -type AdminService struct { - repos *repo.AllRepos -} - -func (svc *AdminService) Create(ctx context.Context, usr types.UserCreate) (*ent.User, error) { - return svc.repos.Users.Create(ctx, usr) -} - -func (svc *AdminService) GetAll(ctx context.Context) ([]*ent.User, error) { - return svc.repos.Users.GetAll(ctx) -} - -func (svc *AdminService) GetByID(ctx context.Context, id uuid.UUID) (*ent.User, error) { - return svc.repos.Users.GetOneId(ctx, id) -} - -func (svc *AdminService) GetByEmail(ctx context.Context, email string) (*ent.User, error) { - return svc.repos.Users.GetOneEmail(ctx, email) -} - -func (svc *AdminService) UpdateProperties(ctx context.Context, ID uuid.UUID, data types.UserUpdate) (*ent.User, error) { - err := svc.repos.Users.Update(ctx, ID, data) - - if err != nil { - return &ent.User{}, err - } - - return svc.repos.Users.GetOneId(ctx, ID) -} - -func (svc *AdminService) Delete(ctx context.Context, id uuid.UUID) error { - return svc.repos.Users.Delete(ctx, id) -} - -func (svc *AdminService) DeleteAll(ctx context.Context) error { - return svc.repos.Users.DeleteAll(ctx) -} diff --git a/backend/internal/services/service_items.go b/backend/internal/services/service_items.go index fc76838..bc1a994 100644 --- a/backend/internal/services/service_items.go +++ b/backend/internal/services/service_items.go @@ -7,8 +7,6 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/services/mappers" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/rs/zerolog/log" ) @@ -26,76 +24,24 @@ type ItemService struct { at attachmentTokens } -func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (*types.ItemOut, error) { - result, err := svc.repo.Items.GetOne(ctx, id) - if err != nil { - return nil, err - } - - if result.Edges.Group.ID != gid { - return nil, ErrNotOwner - } - - return mappers.ToItemOut(result), nil +func (svc *ItemService) GetOne(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.ItemOut, error) { + return svc.repo.Items.GetOneByGroup(ctx, gid, id) } -func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]*types.ItemSummary, error) { - items, err := svc.repo.Items.GetAll(ctx, gid) - if err != nil { - return nil, err - } - - itemsOut := make([]*types.ItemSummary, len(items)) - for i, item := range items { - itemsOut[i] = mappers.ToItemSummary(item) - } - - return itemsOut, nil +func (svc *ItemService) GetAll(ctx context.Context, gid uuid.UUID) ([]repo.ItemSummary, error) { + return svc.repo.Items.GetAll(ctx, gid) } -func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data types.ItemCreate) (*types.ItemOut, error) { - item, err := svc.repo.Items.Create(ctx, gid, data) - if err != nil { - return nil, err - } - - return mappers.ToItemOut(item), nil +func (svc *ItemService) Create(ctx context.Context, gid uuid.UUID, data repo.ItemCreate) (repo.ItemOut, error) { + return svc.repo.Items.Create(ctx, gid, data) } func (svc *ItemService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error { - item, err := svc.repo.Items.GetOne(ctx, id) - if err != nil { - return err - } - - if item.Edges.Group.ID != gid { - return ErrNotOwner - } - - err = svc.repo.Items.Delete(ctx, id) - if err != nil { - return err - } - - return nil + return svc.repo.Items.DeleteByGroup(ctx, gid, id) } -func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data types.ItemUpdate) (*types.ItemOut, error) { - item, err := svc.repo.Items.GetOne(ctx, data.ID) - if err != nil { - return nil, err - } - - if item.Edges.Group.ID != gid { - return nil, ErrNotOwner - } - - item, err = svc.repo.Items.Update(ctx, data) - if err != nil { - return nil, err - } - - return mappers.ToItemOut(item), nil +func (svc *ItemService) Update(ctx context.Context, gid uuid.UUID, data repo.ItemUpdate) (repo.ItemOut, error) { + return svc.repo.Items.UpdateByGroup(ctx, gid, data) } func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]string) error { @@ -144,7 +90,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s fmt.Println("Creating Location: ", row.Location) - result, err := svc.repo.Locations.Create(ctx, gid, types.LocationCreate{ + result, err := svc.repo.Locations.Create(ctx, gid, repo.LocationCreate{ Name: row.Location, Description: "", }) @@ -159,7 +105,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s if _, ok := labels[label]; ok { continue } - result, err := svc.repo.Labels.Create(ctx, gid, types.LabelCreate{ + result, err := svc.repo.Labels.Create(ctx, gid, repo.LabelCreate{ Name: label, Description: "", }) @@ -185,7 +131,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s Str("locationId", locationID.String()). Msgf("Creating Item: %s", row.Item.Name) - result, err := svc.repo.Items.Create(ctx, gid, types.ItemCreate{ + result, err := svc.repo.Items.Create(ctx, gid, repo.ItemCreate{ ImportRef: row.Item.ImportRef, Name: row.Item.Name, Description: row.Item.Description, @@ -198,7 +144,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s } // Update the item with the rest of the data - _, err = svc.repo.Items.Update(ctx, types.ItemUpdate{ + _, err = svc.repo.Items.UpdateByGroup(ctx, gid, repo.ItemUpdate{ // Edges LocationID: locationID, LabelIDs: labelIDs, @@ -209,6 +155,7 @@ func (svc *ItemService) CsvImport(ctx context.Context, gid uuid.UUID, data [][]s Description: result.Description, Insured: row.Item.Insured, Notes: row.Item.Notes, + Quantity: row.Item.Quantity, // Identifies the item as imported SerialNumber: row.Item.SerialNumber, diff --git a/backend/internal/services/service_items_attachments.go b/backend/internal/services/service_items_attachments.go index 9969ee9..0f2afde 100644 --- a/backend/internal/services/service_items_attachments.go +++ b/backend/internal/services/service_items_attachments.go @@ -4,14 +4,13 @@ import ( "context" "io" "os" - "path/filepath" "time" "github.com/google/uuid" + "github.com/hay-kot/homebox/backend/ent" "github.com/hay-kot/homebox/backend/ent/attachment" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/hay-kot/homebox/backend/pkgs/hasher" - "github.com/hay-kot/homebox/backend/pkgs/pathlib" "github.com/rs/zerolog/log" ) @@ -41,13 +40,10 @@ func (at attachmentTokens) Delete(token string) { } func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.UUID) (string, error) { - item, err := svc.repo.Items.GetOne(ctx, itemId) + _, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId) if err != nil { return "", err } - if item.Edges.Group.ID != ctx.GID { - return "", ErrNotOwner - } token := hasher.GenerateToken() @@ -67,52 +63,32 @@ func (svc *ItemService) AttachmentToken(ctx Context, itemId, attachmentId uuid.U return token.Raw, nil } -func (svc *ItemService) attachmentPath(gid, itemId uuid.UUID, filename string) string { - path := filepath.Join(svc.filepath, gid.String(), itemId.String(), filename) - path = pathlib.Safe(path) - log.Debug().Str("path", path).Msg("attachment path") - return path -} - -func (svc *ItemService) AttachmentPath(ctx context.Context, token string) (string, error) { +func (svc *ItemService) AttachmentPath(ctx context.Context, token string) (*ent.Document, error) { attachmentId, ok := svc.at.Get(token) if !ok { - return "", ErrNotFound + return nil, ErrNotFound } attachment, err := svc.repo.Attachments.Get(ctx, attachmentId) - if err != nil { - return "", err - } - - return attachment.Edges.Document.Path, nil -} - -func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *types.ItemAttachmentUpdate) (*types.ItemOut, error) { - // Update Properties - attachment, err := svc.repo.Attachments.Update(ctx, data.ID, attachment.Type(data.Type)) if err != nil { return nil, err } + return attachment.Edges.Document, nil +} + +func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *repo.ItemAttachmentUpdate) (repo.ItemOut, error) { + // Update Attachment + attachment, err := svc.repo.Attachments.Update(ctx, data.ID, attachment.Type(data.Type)) + if err != nil { + return repo.ItemOut{}, err + } + + // Update Document attDoc := attachment.Edges.Document - - if data.Title != attachment.Edges.Document.Title { - newPath := pathlib.Safe(svc.attachmentPath(ctx.GID, itemId, data.Title)) - - // Move File - err = os.Rename(attachment.Edges.Document.Path, newPath) - if err != nil { - return nil, err - } - - _, err = svc.repo.Docs.Update(ctx, attDoc.ID, types.DocumentUpdate{ - Title: data.Title, - Path: newPath, - }) - if err != nil { - return nil, err - } + _, err = svc.repo.Docs.Rename(ctx, attDoc.ID, data.Title) + if err != nil { + return repo.ItemOut{}, err } return svc.GetOne(ctx, ctx.GID, itemId) @@ -121,50 +97,25 @@ func (svc *ItemService) AttachmentUpdate(ctx Context, itemId uuid.UUID, data *ty // AttachmentAdd adds an attachment to an item by creating an entry in the Documents table and linking it to the Attachment // Table and Items table. The file provided via the reader is stored on the file system based on the provided // relative path during construction of the service. -func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (*types.ItemOut, error) { +func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename string, attachmentType attachment.Type, file io.Reader) (repo.ItemOut, error) { // Get the Item - item, err := svc.repo.Items.GetOne(ctx, itemId) + _, err := svc.repo.Items.GetOneByGroup(ctx, ctx.GID, itemId) if err != nil { - return nil, err + return repo.ItemOut{}, err } - if item.Edges.Group.ID != ctx.GID { - return nil, ErrNotOwner - } - - fp := svc.attachmentPath(ctx.GID, itemId, filename) - filename = filepath.Base(fp) - // Create the document - doc, err := svc.repo.Docs.Create(ctx, ctx.GID, types.DocumentCreate{ - Title: filename, - Path: fp, - }) + doc, err := svc.repo.Docs.Create(ctx, ctx.GID, repo.DocumentCreate{Title: filename, Content: file}) if err != nil { - return nil, err + log.Err(err).Msg("failed to create document") + return repo.ItemOut{}, err } // Create the attachment _, err = svc.repo.Attachments.Create(ctx, itemId, doc.ID, attachmentType) if err != nil { - return nil, err - } - - // Read the contents and write them to a file on the file system - err = os.MkdirAll(filepath.Dir(doc.Path), os.ModePerm) - if err != nil { - return nil, err - } - - f, err := os.Create(doc.Path) - if err != nil { - log.Err(err).Msg("failed to create file") - return nil, err - } - - _, err = io.Copy(f, file) - if err != nil { - return nil, err + log.Err(err).Msg("failed to create attachment") + return repo.ItemOut{}, err } return svc.GetOne(ctx, ctx.GID, itemId) @@ -172,15 +123,11 @@ func (svc *ItemService) AttachmentAdd(ctx Context, itemId uuid.UUID, filename st func (svc *ItemService) AttachmentDelete(ctx context.Context, gid, itemId, attachmentId uuid.UUID) error { // Get the Item - item, err := svc.repo.Items.GetOne(ctx, itemId) + _, err := svc.repo.Items.GetOneByGroup(ctx, gid, itemId) if err != nil { return err } - if item.Edges.Group.ID != gid { - return ErrNotOwner - } - attachment, err := svc.repo.Attachments.Get(ctx, attachmentId) if err != nil { return err diff --git a/backend/internal/services/service_items_attachments_test.go b/backend/internal/services/service_items_attachments_test.go index 71f9b60..c3ab032 100644 --- a/backend/internal/services/service_items_attachments_test.go +++ b/backend/internal/services/service_items_attachments_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/internal/repo" "github.com/stretchr/testify/assert" ) @@ -19,14 +19,14 @@ func TestItemService_AddAttachment(t *testing.T) { filepath: temp, } - loc, err := tSvc.Location.Create(context.Background(), tGroup.ID, types.LocationCreate{ + loc, err := tSvc.Location.Create(context.Background(), tGroup.ID, repo.LocationCreate{ Description: "test", Name: "test", }) assert.NoError(t, err) assert.NotNil(t, loc) - itmC := types.ItemCreate{ + itmC := repo.ItemCreate{ Name: fk.Str(10), Description: fk.Str(10), LocationID: loc.ID, @@ -52,7 +52,7 @@ func TestItemService_AddAttachment(t *testing.T) { storedPath := afterAttachment.Attachments[0].Document.Path // {root}/{group}/{item}/{attachment} - assert.Equal(t, path.Join(temp, tGroup.ID.String(), itm.ID.String(), "testfile.txt"), storedPath) + assert.Equal(t, path.Join(temp, "homebox", tGroup.ID.String(), "documents"), path.Dir(storedPath)) // Check that the file contents are correct bts, err := os.ReadFile(storedPath) diff --git a/backend/internal/services/service_items_csv.go b/backend/internal/services/service_items_csv.go index e1d1a96..4ad1aed 100644 --- a/backend/internal/services/service_items_csv.go +++ b/backend/internal/services/service_items_csv.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/hay-kot/homebox/backend/internal/types" + "github.com/hay-kot/homebox/backend/internal/repo" ) var ErrInvalidCsv = errors.New("invalid csv") @@ -45,7 +45,7 @@ func parseInt(s string) int { } type csvRow struct { - Item types.ItemSummary + Item repo.ItemOut Location string LabelStr string } @@ -54,12 +54,14 @@ func newCsvRow(row []string) csvRow { return csvRow{ Location: row[1], LabelStr: row[2], - Item: types.ItemSummary{ - ImportRef: row[0], - Quantity: parseInt(row[3]), - Name: row[4], - Description: row[5], - Insured: parseBool(row[6]), + Item: repo.ItemOut{ + ItemSummary: repo.ItemSummary{ + ImportRef: row[0], + Quantity: parseInt(row[3]), + Name: row[4], + Description: row[5], + Insured: parseBool(row[6]), + }, SerialNumber: row[7], ModelNumber: row[8], Manufacturer: row[9], diff --git a/backend/internal/services/service_items_test.go b/backend/internal/services/service_items_test.go index 5a21508..15fe3ca 100644 --- a/backend/internal/services/service_items_test.go +++ b/backend/internal/services/service_items_test.go @@ -71,19 +71,8 @@ func TestItemService_CsvImport(t *testing.T) { for _, csvRow := range dataCsv { if csvRow.Item.Name == item.Name { assert.Equal(t, csvRow.Item.Description, item.Description) - assert.Equal(t, csvRow.Item.SerialNumber, item.SerialNumber) - assert.Equal(t, csvRow.Item.Manufacturer, item.Manufacturer) - assert.Equal(t, csvRow.Item.Notes, item.Notes) - - // Purchase Fields - assert.Equal(t, csvRow.Item.PurchaseTime, item.PurchaseTime) - assert.Equal(t, csvRow.Item.PurchaseFrom, item.PurchaseFrom) - assert.Equal(t, csvRow.Item.PurchasePrice, item.PurchasePrice) - - // Sold Fields - assert.Equal(t, csvRow.Item.SoldTime, item.SoldTime) - assert.Equal(t, csvRow.Item.SoldTo, item.SoldTo) - assert.Equal(t, csvRow.Item.SoldPrice, item.SoldPrice) + assert.Equal(t, csvRow.Item.Quantity, item.Quantity) + assert.Equal(t, csvRow.Item.Insured, item.Insured) } } } diff --git a/backend/internal/services/service_labels.go b/backend/internal/services/service_labels.go index 5a284e7..34d67df 100644 --- a/backend/internal/services/service_labels.go +++ b/backend/internal/services/service_labels.go @@ -5,59 +5,33 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/services/mappers" - "github.com/hay-kot/homebox/backend/internal/types" ) type LabelService struct { repos *repo.AllRepos } -func (svc *LabelService) Create(ctx context.Context, groupId uuid.UUID, data types.LabelCreate) (*types.LabelSummary, error) { - label, err := svc.repos.Labels.Create(ctx, groupId, data) - return mappers.ToLabelSummaryErr(label, err) +func (svc *LabelService) Create(ctx context.Context, groupId uuid.UUID, data repo.LabelCreate) (repo.LabelOut, error) { + return svc.repos.Labels.Create(ctx, groupId, data) } -func (svc *LabelService) Update(ctx context.Context, groupId uuid.UUID, data types.LabelUpdate) (*types.LabelSummary, error) { - label, err := svc.repos.Labels.Update(ctx, data) - return mappers.ToLabelSummaryErr(label, err) +func (svc *LabelService) Update(ctx context.Context, groupId uuid.UUID, data repo.LabelUpdate) (repo.LabelOut, error) { + return svc.repos.Labels.Update(ctx, data) } -func (svc *LabelService) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error { - label, err := svc.repos.Labels.Get(ctx, id) +func (svc *LabelService) Delete(ctx context.Context, gid uuid.UUID, id uuid.UUID) error { + _, err := svc.repos.Labels.GetOneByGroup(ctx, gid, id) if err != nil { return err } - if label.Edges.Group.ID != groupId { - return ErrNotOwner - } return svc.repos.Labels.Delete(ctx, id) } -func (svc *LabelService) Get(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (*types.LabelOut, error) { - label, err := svc.repos.Labels.Get(ctx, id) +func (svc *LabelService) Get(ctx context.Context, gid uuid.UUID, id uuid.UUID) (repo.LabelOut, error) { + return svc.repos.Labels.GetOneByGroup(ctx, gid, id) - if err != nil { - return nil, err - } - - if label.Edges.Group.ID != groupId { - return nil, ErrNotOwner - } - - return mappers.ToLabelOut(label), nil } -func (svc *LabelService) GetAll(ctx context.Context, groupId uuid.UUID) ([]*types.LabelSummary, error) { - labels, err := svc.repos.Labels.GetAll(ctx, groupId) - if err != nil { - return nil, err - } - - labelsOut := make([]*types.LabelSummary, len(labels)) - for i, label := range labels { - labelsOut[i] = mappers.ToLabelSummary(label) - } - - return labelsOut, nil +func (svc *LabelService) GetAll(ctx context.Context, groupId uuid.UUID) ([]repo.LabelSummary, error) { + return svc.repos.Labels.GetAll(ctx, groupId) } diff --git a/backend/internal/services/service_locations.go b/backend/internal/services/service_locations.go index 2349f6b..7152337 100644 --- a/backend/internal/services/service_locations.go +++ b/backend/internal/services/service_locations.go @@ -6,8 +6,6 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/services/mappers" - "github.com/hay-kot/homebox/backend/internal/types" ) var ( @@ -18,59 +16,32 @@ type LocationService struct { repos *repo.AllRepos } -func (svc *LocationService) GetOne(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (*types.LocationOut, error) { - location, err := svc.repos.Locations.Get(ctx, id) - - if err != nil { - return nil, err - } - - if location.Edges.Group.ID != groupId { - return nil, ErrNotOwner - } - - return mappers.ToLocationOut(location), nil +func (svc *LocationService) GetOne(ctx context.Context, groupId uuid.UUID, id uuid.UUID) (repo.LocationOut, error) { + return svc.repos.Locations.GetOneByGroup(ctx, groupId, id) } -func (svc *LocationService) GetAll(ctx context.Context, groupId uuid.UUID) ([]*types.LocationCount, error) { - locations, err := svc.repos.Locations.GetAll(ctx, groupId) - if err != nil { - return nil, err - } - - locationsOut := make([]*types.LocationCount, len(locations)) - for i, location := range locations { - locationsOut[i] = mappers.ToLocationCount(&location) - } - - return locationsOut, nil +func (svc *LocationService) GetAll(ctx context.Context, groupId uuid.UUID) ([]repo.LocationOutCount, error) { + return svc.repos.Locations.GetAll(ctx, groupId) } -func (svc *LocationService) Create(ctx context.Context, groupId uuid.UUID, data types.LocationCreate) (*types.LocationOut, error) { - location, err := svc.repos.Locations.Create(ctx, groupId, data) - return mappers.ToLocationOutErr(location, err) +func (svc *LocationService) Create(ctx context.Context, groupId uuid.UUID, data repo.LocationCreate) (repo.LocationOut, error) { + return svc.repos.Locations.Create(ctx, groupId, data) } func (svc *LocationService) Delete(ctx context.Context, groupId uuid.UUID, id uuid.UUID) error { - location, err := svc.repos.Locations.Get(ctx, id) + _, err := svc.repos.Locations.GetOneByGroup(ctx, groupId, id) if err != nil { return err } - if location.Edges.Group.ID != groupId { - return ErrNotOwner - } - return svc.repos.Locations.Delete(ctx, id) } -func (svc *LocationService) Update(ctx context.Context, groupId uuid.UUID, data types.LocationUpdate) (*types.LocationOut, error) { - location, err := svc.repos.Locations.Get(ctx, data.ID) +func (svc *LocationService) Update(ctx context.Context, groupId uuid.UUID, data repo.LocationUpdate) (repo.LocationOut, error) { + location, err := svc.repos.Locations.GetOneByGroup(ctx, groupId, data.ID) if err != nil { - return nil, err - } - if location.Edges.Group.ID != groupId { - return nil, ErrNotOwner + return repo.LocationOut{}, err } - return mappers.ToLocationOutErr(svc.repos.Locations.Update(ctx, data)) + data.ID = location.ID + return svc.repos.Locations.Update(ctx, data) } diff --git a/backend/internal/services/service_user.go b/backend/internal/services/service_user.go index 8d1abcf..3e55ba8 100644 --- a/backend/internal/services/service_user.go +++ b/backend/internal/services/service_user.go @@ -7,9 +7,8 @@ import ( "github.com/google/uuid" "github.com/hay-kot/homebox/backend/internal/repo" - "github.com/hay-kot/homebox/backend/internal/services/mappers" - "github.com/hay-kot/homebox/backend/internal/types" "github.com/hay-kot/homebox/backend/pkgs/hasher" + "github.com/rs/zerolog/log" ) var ( @@ -23,18 +22,46 @@ type UserService struct { repos *repo.AllRepos } +type ( + UserRegistration struct { + Name string `json:"name"` + Email string `json:"email"` + Password string `json:"password"` + GroupName string `json:"groupName"` + } + UserAuthTokenDetail struct { + Raw string `json:"raw"` + ExpiresAt time.Time `json:"expiresAt"` + } + UserAuthTokenCreate struct { + TokenHash []byte `json:"token"` + UserID uuid.UUID `json:"userId"` + ExpiresAt time.Time `json:"expiresAt"` + } + LoginForm struct { + Username string `json:"username"` + Password string `json:"password"` + } +) + // RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group // with default Labels and Locations. -func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistration) (*types.UserOut, error) { +func (svc *UserService) RegisterUser(ctx context.Context, data UserRegistration) (repo.UserOut, error) { + log.Debug(). + Str("name", data.Name). + Str("email", data.Email). + Str("groupName", data.GroupName). + Msg("Registering new user") + group, err := svc.repos.Groups.Create(ctx, data.GroupName) if err != nil { - return &types.UserOut{}, err + return repo.UserOut{}, err } - hashed, _ := hasher.HashPassword(data.User.Password) - usrCreate := types.UserCreate{ - Name: data.User.Name, - Email: data.User.Email, + hashed, _ := hasher.HashPassword(data.Password) + usrCreate := repo.UserCreate{ + Name: data.Name, + Email: data.Email, Password: hashed, IsSuperuser: false, GroupID: group.ID, @@ -42,61 +69,61 @@ func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistr usr, err := svc.repos.Users.Create(ctx, usrCreate) if err != nil { - return &types.UserOut{}, err + return repo.UserOut{}, err } for _, label := range defaultLabels() { _, err := svc.repos.Labels.Create(ctx, group.ID, label) if err != nil { - return &types.UserOut{}, err + return repo.UserOut{}, err } } for _, location := range defaultLocations() { _, err := svc.repos.Locations.Create(ctx, group.ID, location) if err != nil { - return &types.UserOut{}, err + return repo.UserOut{}, err } } - return mappers.ToOutUser(usr, nil) + return usr, nil } // GetSelf returns the user that is currently logged in based of the token provided within -func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (*types.UserOut, error) { +func (svc *UserService) GetSelf(ctx context.Context, requestToken string) (repo.UserOut, error) { hash := hasher.HashToken(requestToken) - return mappers.ToOutUser(svc.repos.AuthTokens.GetUserFromToken(ctx, hash)) + return svc.repos.AuthTokens.GetUserFromToken(ctx, hash) } -func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data types.UserUpdate) (*types.UserOut, error) { +func (svc *UserService) UpdateSelf(ctx context.Context, ID uuid.UUID, data repo.UserUpdate) (repo.UserOut, error) { err := svc.repos.Users.Update(ctx, ID, data) if err != nil { - return &types.UserOut{}, err + return repo.UserOut{}, err } - return mappers.ToOutUser(svc.repos.Users.GetOneId(ctx, ID)) + return svc.repos.Users.GetOneId(ctx, ID) } // ============================================================================ // User Authentication -func (svc *UserService) createToken(ctx context.Context, userId uuid.UUID) (types.UserAuthTokenDetail, error) { +func (svc *UserService) createToken(ctx context.Context, userId uuid.UUID) (UserAuthTokenDetail, error) { newToken := hasher.GenerateToken() - created, err := svc.repos.AuthTokens.CreateToken(ctx, types.UserAuthTokenCreate{ + created, err := svc.repos.AuthTokens.CreateToken(ctx, repo.UserAuthTokenCreate{ UserID: userId, TokenHash: newToken.Hash, ExpiresAt: time.Now().Add(oneWeek), }) - return types.UserAuthTokenDetail{Raw: newToken.Raw, ExpiresAt: created.ExpiresAt}, err + return UserAuthTokenDetail{Raw: newToken.Raw, ExpiresAt: created.ExpiresAt}, err } -func (svc *UserService) Login(ctx context.Context, username, password string) (types.UserAuthTokenDetail, error) { +func (svc *UserService) Login(ctx context.Context, username, password string) (UserAuthTokenDetail, error) { usr, err := svc.repos.Users.GetOneEmail(ctx, username) - if err != nil || !hasher.CheckPasswordHash(password, usr.Password) { - return types.UserAuthTokenDetail{}, ErrorInvalidLogin + if err != nil || !hasher.CheckPasswordHash(password, usr.PasswordHash) { + return UserAuthTokenDetail{}, ErrorInvalidLogin } return svc.createToken(ctx, usr.ID) @@ -108,13 +135,13 @@ func (svc *UserService) Logout(ctx context.Context, token string) error { return err } -func (svc *UserService) RenewToken(ctx context.Context, token string) (types.UserAuthTokenDetail, error) { +func (svc *UserService) RenewToken(ctx context.Context, token string) (UserAuthTokenDetail, error) { hash := hasher.HashToken(token) dbToken, err := svc.repos.AuthTokens.GetUserFromToken(ctx, hash) if err != nil { - return types.UserAuthTokenDetail{}, ErrorInvalidToken + return UserAuthTokenDetail{}, ErrorInvalidToken } newToken, _ := svc.createToken(ctx, dbToken.ID) diff --git a/backend/internal/services/service_user_defaults.go b/backend/internal/services/service_user_defaults.go index 2b5cbbe..9882d5e 100644 --- a/backend/internal/services/service_user_defaults.go +++ b/backend/internal/services/service_user_defaults.go @@ -1,9 +1,11 @@ package services -import "github.com/hay-kot/homebox/backend/internal/types" +import ( + "github.com/hay-kot/homebox/backend/internal/repo" +) -func defaultLocations() []types.LocationCreate { - return []types.LocationCreate{ +func defaultLocations() []repo.LocationCreate { + return []repo.LocationCreate{ { Name: "Living Room", }, @@ -31,8 +33,8 @@ func defaultLocations() []types.LocationCreate { } } -func defaultLabels() []types.LabelCreate { - return []types.LabelCreate{ +func defaultLabels() []repo.LabelCreate { + return []repo.LabelCreate{ { Name: "Appliances", }, diff --git a/backend/internal/types/about_types.go b/backend/internal/types/about_types.go deleted file mode 100644 index f3d33e9..0000000 --- a/backend/internal/types/about_types.go +++ /dev/null @@ -1,18 +0,0 @@ -package types - -// ApiSummary -// -// @public -type ApiSummary struct { - Healthy bool `json:"health"` - Versions []string `json:"versions"` - Title string `json:"title"` - Message string `json:"message"` - Build Build -} - -type Build struct { - Version string `json:"version"` - Commit string `json:"commit"` - BuildTime string `json:"buildTime"` -} diff --git a/backend/internal/types/document_types.go b/backend/internal/types/document_types.go deleted file mode 100644 index c34a37a..0000000 --- a/backend/internal/types/document_types.go +++ /dev/null @@ -1,31 +0,0 @@ -package types - -import ( - "time" - - "github.com/google/uuid" -) - -type DocumentOut struct { - ID uuid.UUID `json:"id"` - Title string `json:"title"` - Path string -} - -type DocumentCreate struct { - Title string `json:"name"` - Path string `json:"path"` -} - -type DocumentUpdate = DocumentCreate - -type DocumentToken struct { - Raw string `json:"raw"` - ExpiresAt time.Time `json:"expiresAt"` -} - -type DocumentTokenCreate struct { - TokenHash []byte `json:"tokenHash"` - DocumentID uuid.UUID `json:"documentId"` - ExpiresAt time.Time `json:"expiresAt"` -} diff --git a/backend/internal/types/item_types.go b/backend/internal/types/item_types.go deleted file mode 100644 index d86696f..0000000 --- a/backend/internal/types/item_types.go +++ /dev/null @@ -1,118 +0,0 @@ -package types - -import ( - "time" - - "github.com/google/uuid" -) - -type ItemCreate struct { - ImportRef string `json:"-"` - Name string `json:"name"` - Description string `json:"description"` - - // Edges - LocationID uuid.UUID `json:"locationId"` - LabelIDs []uuid.UUID `json:"labelIds"` -} - -type ItemUpdate struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Quantity int `json:"quantity"` - Insured bool `json:"insured"` - - // Edges - LocationID uuid.UUID `json:"locationId"` - LabelIDs []uuid.UUID `json:"labelIds"` - - // Identifications - SerialNumber string `json:"serialNumber"` - ModelNumber string `json:"modelNumber"` - Manufacturer string `json:"manufacturer"` - - // Warranty - LifetimeWarranty bool `json:"lifetimeWarranty"` - WarrantyExpires time.Time `json:"warrantyExpires"` - WarrantyDetails string `json:"warrantyDetails"` - - // Purchase - PurchaseTime time.Time `json:"purchaseTime"` - PurchaseFrom string `json:"purchaseFrom"` - PurchasePrice float64 `json:"purchasePrice,string"` - - // Sold - SoldTime time.Time `json:"soldTime"` - SoldTo string `json:"soldTo"` - SoldPrice float64 `json:"soldPrice,string"` - SoldNotes string `json:"soldNotes"` - - // Extras - Notes string `json:"notes"` - // Fields []*FieldSummary `json:"fields"` -} - -type ItemSummary struct { - ImportRef string `json:"-"` - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Quantity int `json:"quantity"` - Insured bool `json:"insured"` - - // Edges - Location *LocationSummary `json:"location"` - Labels []*LabelSummary `json:"labels"` - - // Identifications - SerialNumber string `json:"serialNumber"` - ModelNumber string `json:"modelNumber"` - Manufacturer string `json:"manufacturer"` - - // Warranty - LifetimeWarranty bool `json:"lifetimeWarranty"` - WarrantyExpires time.Time `json:"warrantyExpires"` - WarrantyDetails string `json:"warrantyDetails"` - - // Purchase - PurchaseTime time.Time `json:"purchaseTime"` - PurchaseFrom string `json:"purchaseFrom"` - PurchasePrice float64 `json:"purchasePrice,string"` - - // Sold - SoldTime time.Time `json:"soldTime"` - SoldTo string `json:"soldTo"` - SoldPrice float64 `json:"soldPrice,string"` - SoldNotes string `json:"soldNotes"` - - // Extras - Notes string `json:"notes"` -} - -type ItemOut struct { - ItemSummary - Attachments []*ItemAttachment `json:"attachments"` - // Future - // Fields []*FieldSummary `json:"fields"` -} - -type ItemAttachment struct { - ID uuid.UUID `json:"id"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` - Type string `json:"type"` - Document DocumentOut `json:"document"` -} - -type ItemAttachmentToken struct { - Token string `json:"token"` -} - -type ItemAttachmentUpdate struct { - ID uuid.UUID `json:"-"` - Type string `json:"type"` - Title string `json:"title"` -} diff --git a/backend/internal/types/label_types.go b/backend/internal/types/label_types.go deleted file mode 100644 index 725361c..0000000 --- a/backend/internal/types/label_types.go +++ /dev/null @@ -1,33 +0,0 @@ -package types - -import ( - "time" - - "github.com/google/uuid" -) - -type LabelCreate struct { - Name string `json:"name"` - Description string `json:"description"` - Color string `json:"color"` -} - -type LabelUpdate struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Color string `json:"color"` -} - -type LabelSummary struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -type LabelOut struct { - LabelSummary - Items []*ItemSummary `json:"items"` -} diff --git a/backend/internal/types/location_types.go b/backend/internal/types/location_types.go deleted file mode 100644 index 0916c2d..0000000 --- a/backend/internal/types/location_types.go +++ /dev/null @@ -1,36 +0,0 @@ -package types - -import ( - "time" - - "github.com/google/uuid" -) - -type LocationCreate struct { - Name string `json:"name"` - Description string `json:"description"` -} - -type LocationUpdate struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` -} - -type LocationSummary struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -type LocationCount struct { - LocationSummary - ItemCount int `json:"itemCount"` -} - -type LocationOut struct { - LocationSummary - Items []*ItemSummary `json:"items"` -} diff --git a/backend/internal/types/token_types.go b/backend/internal/types/token_types.go deleted file mode 100644 index 56b0b49..0000000 --- a/backend/internal/types/token_types.go +++ /dev/null @@ -1,39 +0,0 @@ -package types - -import ( - "time" - - "github.com/google/uuid" -) - -type LoginForm struct { - Username string `json:"username"` - Password string `json:"password"` -} - -type TokenResponse struct { - BearerToken string `json:"token"` - ExpiresAt time.Time `json:"expiresAt"` -} - -type UserAuthTokenDetail struct { - Raw string `json:"raw"` - ExpiresAt time.Time `json:"expiresAt"` -} - -type UserAuthToken struct { - TokenHash []byte `json:"token"` - UserID uuid.UUID `json:"userId"` - ExpiresAt time.Time `json:"expiresAt"` - CreatedAt time.Time `json:"createdAt"` -} - -func (u UserAuthToken) IsExpired() bool { - return u.ExpiresAt.Before(time.Now()) -} - -type UserAuthTokenCreate struct { - TokenHash []byte `json:"token"` - UserID uuid.UUID `json:"userId"` - ExpiresAt time.Time `json:"expiresAt"` -} diff --git a/backend/internal/types/users_types.go b/backend/internal/types/users_types.go deleted file mode 100644 index 0a10ec4..0000000 --- a/backend/internal/types/users_types.go +++ /dev/null @@ -1,60 +0,0 @@ -package types - -import ( - "errors" - - "github.com/google/uuid" -) - -var ( - ErrNameEmpty = errors.New("name is empty") - ErrEmailEmpty = errors.New("email is empty") -) - -// UserIn is a basic user input struct containing only the fields that are -// required for user creation. -type UserIn struct { - Name string `json:"name"` - Email string `json:"email"` - Password string `json:"password"` -} - -// UserCreate is the Data object contain the requirements of creating a user -// in the database. It should to create users from an API unless the user has -// rights to create SuperUsers. For regular user in data use the UserIn struct. -type UserCreate struct { - Name string `json:"name"` - Email string `json:"email"` - Password string `json:"password"` - IsSuperuser bool `json:"isSuperuser"` - GroupID uuid.UUID `json:"groupID"` -} - -func (u UserCreate) Validate() error { - if u.Name == "" { - return ErrNameEmpty - } - if u.Email == "" { - return ErrEmailEmpty - } - return nil -} - -type UserUpdate struct { - Name string `json:"name"` - Email string `json:"email"` -} - -type UserRegistration struct { - User UserIn `json:"user"` - GroupName string `json:"groupName"` -} - -type UserOut struct { - ID uuid.UUID `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - IsSuperuser bool `json:"isSuperuser"` - GroupID uuid.UUID `json:"groupId"` - GroupName string `json:"groupName"` -} diff --git a/backend/internal/types/users_types_test.go b/backend/internal/types/users_types_test.go deleted file mode 100644 index 9f4a942..0000000 --- a/backend/internal/types/users_types_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package types - -import ( - "testing" -) - -func TestUserCreate_Validate(t *testing.T) { - type fields struct { - Name string - Email string - Password string - IsSuperuser bool - } - tests := []struct { - name string - fields fields - wantErr bool - }{ - { - name: "no_name", - fields: fields{ - Name: "", - Email: "", - Password: "", - IsSuperuser: false, - }, - wantErr: true, - }, - { - name: "no_email", - fields: fields{ - Name: "test", - Email: "", - Password: "", - IsSuperuser: false, - }, - wantErr: true, - }, - { - name: "valid", - fields: fields{ - Name: "test", - Email: "test@email.com", - Password: "mypassword", - IsSuperuser: false, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u := &UserCreate{ - Name: tt.fields.Name, - Email: tt.fields.Email, - Password: tt.fields.Password, - IsSuperuser: tt.fields.IsSuperuser, - } - if err := u.Validate(); (err != nil) != tt.wantErr { - t.Errorf("UserCreate.Validate() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/backend/pkgs/set/funcs.go b/backend/pkgs/set/funcs.go new file mode 100644 index 0000000..0d9a261 --- /dev/null +++ b/backend/pkgs/set/funcs.go @@ -0,0 +1,101 @@ +package set + +// Diff returns the difference between two sets +func Diff[T key](a, b Set[T]) Set[T] { + s := New[T]() + for k := range a.mp { + if !b.Contains(k) { + s.Insert(k) + } + } + return s +} + +// Intersect returns the intersection between two sets +func Intersect[T key](a, b Set[T]) Set[T] { + s := New[T]() + for k := range a.mp { + if b.Contains(k) { + s.Insert(k) + } + } + return s +} + +// Union returns the union between two sets +func Union[T key](a, b Set[T]) Set[T] { + s := New[T]() + for k := range a.mp { + s.Insert(k) + } + for k := range b.mp { + s.Insert(k) + } + return s +} + +// Xor returns the symmetric difference between two sets +func Xor[T key](a, b Set[T]) Set[T] { + s := New[T]() + for k := range a.mp { + if !b.Contains(k) { + s.Insert(k) + } + } + for k := range b.mp { + if !a.Contains(k) { + s.Insert(k) + } + } + return s +} + +// Equal returns true if two sets are equal +func Equal[T key](a, b Set[T]) bool { + if a.Len() != b.Len() { + return false + } + for k := range a.mp { + if !b.Contains(k) { + return false + } + } + return true +} + +// Subset returns true if a is a subset of b +func Subset[T key](a, b Set[T]) bool { + if a.Len() > b.Len() { + return false + } + for k := range a.mp { + if !b.Contains(k) { + return false + } + } + return true +} + +// Superset returns true if a is a superset of b +func Superset[T key](a, b Set[T]) bool { + if a.Len() < b.Len() { + return false + } + for k := range b.mp { + if !a.Contains(k) { + return false + } + } + return true +} + +// Disjoint returns true if two sets are disjoint +func Disjoint[T key](a, b Set[T]) bool { + for k := range a.mp { + if b.Contains(k) { + return false + } + } + return true + +} diff --git a/backend/pkgs/set/funcs_test.go b/backend/pkgs/set/funcs_test.go new file mode 100644 index 0000000..ab3aa0e --- /dev/null +++ b/backend/pkgs/set/funcs_test.go @@ -0,0 +1,287 @@ +package set + +import ( + "reflect" + "testing" +) + +type args struct { + a Set[string] + b Set[string] +} + +var ( + argsBasic = args{ + a: New("a", "b", "c"), + b: New("b", "c", "d"), + } + + argsNoOverlap = args{ + a: New("a", "b", "c"), + b: New("d", "e", "f"), + } + + argsIdentical = args{ + a: New("a", "b", "c"), + b: New("a", "b", "c"), + } +) + +func TestDiff(t *testing.T) { + + tests := []struct { + name string + args args + want Set[string] + }{ + { + name: "diff basic", + args: argsBasic, + want: New("a"), + }, + { + name: "diff empty", + args: argsIdentical, + want: New[string](), + }, + { + name: "diff no overlap", + args: argsNoOverlap, + want: New("a", "b", "c"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Diff(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Diff() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIntersect(t *testing.T) { + tests := []struct { + name string + args args + want Set[string] + }{ + { + name: "intersect basic", + args: argsBasic, + want: New("b", "c"), + }, + { + name: "identical sets", + args: argsIdentical, + want: New("a", "b", "c"), + }, + { + name: "no overlap", + args: argsNoOverlap, + want: New[string](), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Intersect(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Intersect() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUnion(t *testing.T) { + tests := []struct { + name string + args args + want Set[string] + }{ + { + name: "intersect basic", + args: argsBasic, + want: New("a", "b", "c", "d"), + }, + { + name: "identical sets", + args: argsIdentical, + want: New("a", "b", "c"), + }, + { + name: "no overlap", + args: argsNoOverlap, + want: New("a", "b", "c", "d", "e", "f"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Union(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Union() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestXor(t *testing.T) { + tests := []struct { + name string + args args + want Set[string] + }{ + { + name: "xor basic", + args: argsBasic, + want: New("a", "d"), + }, + { + name: "identical sets", + args: argsIdentical, + want: New[string](), + }, + { + name: "no overlap", + args: argsNoOverlap, + want: New("a", "b", "c", "d", "e", "f"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Xor(tt.args.a, tt.args.b); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Xor() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEqual(t *testing.T) { + tests := []struct { + name string + args args + want bool + }{ + { + name: "equal basic", + args: argsBasic, + want: false, + }, + { + name: "identical sets", + args: argsIdentical, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Equal(tt.args.a, tt.args.b); got != tt.want { + t.Errorf("Equal() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSubset(t *testing.T) { + type args struct { + a Set[string] + b Set[string] + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "subset basic", + args: args{ + a: New("a", "b"), + b: New("a", "b", "c"), + }, + want: true, + }, + { + name: "subset basic false", + args: args{ + a: New("a", "b", "d"), + b: New("a", "b", "c"), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Subset(tt.args.a, tt.args.b); got != tt.want { + t.Errorf("Subset() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSuperset(t *testing.T) { + type args struct { + a Set[string] + b Set[string] + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "superset basic", + args: args{ + a: New("a", "b", "c"), + b: New("a", "b"), + }, + want: true, + }, + { + name: "superset basic false", + args: args{ + a: New("a", "b", "c"), + b: New("a", "b", "d"), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Superset(tt.args.a, tt.args.b); got != tt.want { + t.Errorf("Superset() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestDisjoint(t *testing.T) { + type args struct { + a Set[string] + b Set[string] + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "disjoint basic", + args: args{ + a: New("a", "b"), + b: New("c", "d"), + }, + want: true, + }, + { + name: "disjoint basic false", + args: args{ + a: New("a", "b"), + b: New("b", "c"), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Disjoint(tt.args.a, tt.args.b); got != tt.want { + t.Errorf("Disjoint() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/backend/pkgs/set/set.go b/backend/pkgs/set/set.go new file mode 100644 index 0000000..f2ffecc --- /dev/null +++ b/backend/pkgs/set/set.go @@ -0,0 +1,56 @@ +package set + +type key interface { + comparable +} + +type Set[T key] struct { + mp map[T]struct{} +} + +func New[T key](v ...T) Set[T] { + mp := make(map[T]struct{}, len(v)) + + s := Set[T]{mp} + + s.Insert(v...) + return s +} + +func (s Set[T]) Insert(v ...T) { + for _, e := range v { + s.mp[e] = struct{}{} + } +} + +func (s Set[T]) Remove(v ...T) { + for _, e := range v { + delete(s.mp, e) + } +} + +func (s Set[T]) Contains(v T) bool { + _, ok := s.mp[v] + return ok +} + +func (s Set[T]) ContainsAll(v ...T) bool { + for _, e := range v { + if !s.Contains(e) { + return false + } + } + return true +} + +func (s Set[T]) Slice() []T { + slice := make([]T, 0, len(s.mp)) + for k := range s.mp { + slice = append(slice, k) + } + return slice +} + +func (s Set[T]) Len() int { + return len(s.mp) +} diff --git a/backend/pkgs/set/set_test.go b/backend/pkgs/set/set_test.go new file mode 100644 index 0000000..b571298 --- /dev/null +++ b/backend/pkgs/set/set_test.go @@ -0,0 +1,255 @@ +package set + +import ( + "reflect" + "testing" +) + +func TestNew(t *testing.T) { + type args struct { + v []string + } + tests := []struct { + name string + args args + want Set[string] + }{ + { + name: "new", + args: args{ + v: []string{"a", "b", "c"}, + }, + want: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + }, + { + name: "new empty", + args: args{ + v: []string{}, + }, + want: Set[string]{ + mp: map[string]struct{}{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.v...); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSet_Insert(t *testing.T) { + type args struct { + v []string + } + tests := []struct { + name string + s Set[string] + args args + want Set[string] + }{ + { + name: "insert", + s: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + args: args{ + v: []string{"d", "e", "f"}, + }, + want: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + "d": {}, + "e": {}, + "f": {}, + }, + }, + }, + { + name: "insert empty", + s: Set[string]{ + mp: map[string]struct{}{}, + }, + args: args{ + v: []string{"a", "b", "c"}, + }, + want: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Insert(tt.args.v...) + if !reflect.DeepEqual(tt.s, tt.want) { + t.Errorf("Set.Insert() = %v, want %v", tt.s, tt.want) + } + }) + } +} + +func TestSet_Delete(t *testing.T) { + type args struct { + v []string + } + tests := []struct { + name string + s Set[string] + args args + want Set[string] + }{ + { + name: "insert", + s: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + "d": {}, + "e": {}, + "f": {}, + }, + }, + args: args{ + v: []string{"d", "e", "f"}, + }, + want: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + }, + { + name: "delete empty", + s: Set[string]{ + mp: map[string]struct{}{}, + }, + args: args{ + v: []string{}, + }, + want: Set[string]{ + mp: map[string]struct{}{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.s.Remove(tt.args.v...) + if !reflect.DeepEqual(tt.s, tt.want) { + t.Errorf("Set.Delete() = %v, want %v", tt.s, tt.want) + } + }) + } +} + +func TestSet_ContainsAll(t *testing.T) { + type args struct { + v []string + } + tests := []struct { + name string + s Set[string] + args args + want bool + }{ + { + name: "contains", + s: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + args: args{ + v: []string{"a", "b", "c"}, + }, + want: true, + }, + { + name: "contains empty", + s: Set[string]{ + mp: map[string]struct{}{}, + }, + args: args{ + v: []string{}, + }, + want: true, + }, + { + name: "not contains", + s: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + args: args{ + v: []string{"d", "e", "f"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.ContainsAll(tt.args.v...); got != tt.want { + t.Errorf("Set.ContainsAll() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSet_Slice(t *testing.T) { + tests := []struct { + name string + s Set[string] + want []string + }{ + { + name: "slice", + s: Set[string]{ + mp: map[string]struct{}{ + "a": {}, + "b": {}, + "c": {}, + }, + }, + want: []string{"a", "b", "c"}, + }, + { + name: "slice empty", + s: Set[string]{ + mp: map[string]struct{}{}, + }, + want: []string{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.s.Slice(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Set.Slice() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/frontend/components/Location/Card.vue b/frontend/components/Location/Card.vue index 78504b2..56b36a2 100644 --- a/frontend/components/Location/Card.vue +++ b/frontend/components/Location/Card.vue @@ -26,11 +26,11 @@